FIRE-30873: Add hand presets

in User pose directory
master
Angeldark Raymaker 2024-10-23 23:17:30 +01:00
parent c4f67ee0ba
commit fc0c595051
3 changed files with 248 additions and 47 deletions

View File

@ -48,6 +48,7 @@ static const std::string POSE_INTERNAL_FORMAT_FILE_MASK = "*.xml";
static const std::string POSE_INTERNAL_FORMAT_FILE_EXT = ".xml";
static const std::string POSE_EXTERNAL_FORMAT_FILE_EXT = ".bvh";
static const std::string POSE_SAVE_SUBDIRECTORY = "poses";
static const std::string POSE_PRESETS_HANDS_SUBDIRECTORY = "poses\\hand_presets";
static const std::string XML_LIST_HEADER_STRING_PREFIX = "header_";
static const std::string XML_LIST_TITLE_STRING_PREFIX = "title_";
static const std::string XML_JOINT_TRANSFORM_STRING_PREFIX = "joint_transform_";
@ -62,7 +63,8 @@ static const std::string POSER_AVATAR_TABGROUP_JOINTS = "joints_tabs";
static const std::string POSER_AVATAR_TAB_POSITION = "positionRotation_panel";
static const std::string POSER_AVATAR_TAB_BODY = "body_joints_panel";
static const std::string POSER_AVATAR_TAB_FACE = "face_joints_panel";
static const std::string POSER_AVATAR_TAB_HANDS = "hands_joints_panel";
static const std::string POSER_AVATAR_TAB_HANDS = "hands_tabs";
static const std::string POSER_AVATAR_TAB_HANDJOINTS = "hands_joints_panel";
static const std::string POSER_AVATAR_TAB_MISC = "misc_joints_panel";
static const std::string POSER_AVATAR_TAB_VOLUMES = "collision_volumes_panel";
@ -116,6 +118,7 @@ static const std::string POSER_AVATAR_SCROLLLIST_FACEJOINTS_NAME = "face_joi
static const std::string POSER_AVATAR_SCROLLLIST_HANDJOINTS_NAME = "hand_joints_scroll";
static const std::string POSER_AVATAR_SCROLLLIST_MISCJOINTS_NAME = "misc_joints_scroll";
static const std::string POSER_AVATAR_SCROLLLIST_VOLUMES_NAME = "collision_volumes_scroll";
static const std::string POSER_AVATAR_SCROLLLIST_HAND_PRESETS_NAME = "hand_presets_scroll";
FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
{
@ -146,6 +149,8 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
mCommitCallbackRegistrar.add("Poser.Save", boost::bind(&FSFloaterPoser::onClickPoseSave, this));
mCommitCallbackRegistrar.add("Pose.Menu", boost::bind(&FSFloaterPoser::onPoseMenuAction, this, _2));
mCommitCallbackRegistrar.add("Poser.BrowseCache", boost::bind(&FSFloaterPoser::onClickBrowsePoseCache, this));
mCommitCallbackRegistrar.add("Poser.LoadLeftHand", boost::bind(&FSFloaterPoser::onClickLoadLeftHandPose, this));
mCommitCallbackRegistrar.add("Poser.LoadRightHand", boost::bind(&FSFloaterPoser::onClickLoadRightHandPose, this));
mCommitCallbackRegistrar.add("Poser.FlipPose", boost::bind(&FSFloaterPoser::onClickFlipPose, this));
mCommitCallbackRegistrar.add("Poser.FlipJoint", boost::bind(&FSFloaterPoser::onClickFlipSelectedJoints, this));
@ -261,6 +266,7 @@ void FSFloaterPoser::onOpen(const LLSD& key)
refreshJointScrollListMembers();
onJointSelect();
onOpenSetAdvancedPanel();
refreshPoseScroll(POSER_AVATAR_SCROLLLIST_HAND_PRESETS_NAME, POSE_PRESETS_HANDS_SUBDIRECTORY);
}
void FSFloaterPoser::onClose(bool app_quitting)
@ -274,15 +280,18 @@ void FSFloaterPoser::onClose(bool app_quitting)
gSavedSettings.setBOOL(POSER_ALSOSAVEBVHFILE_SAVE_KEY, saveBvhCheckbox->getValue());
}
void FSFloaterPoser::refreshPosesScroll()
void FSFloaterPoser::refreshPoseScroll(std::string scrollListName, std::string subDirectory)
{
LLScrollListCtrl *posesScrollList = getChild<LLScrollListCtrl>(POSER_AVATAR_SCROLLLIST_LOADSAVE_NAME);
if (scrollListName.empty() || subDirectory.empty())
return;
LLScrollListCtrl* posesScrollList = getChild<LLScrollListCtrl>(scrollListName);
if (!posesScrollList)
return;
posesScrollList->clearRows();
std::string dir = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "poses");
std::string dir = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, subDirectory);
std::string file;
LLDirIterator dir_iter(dir, POSE_INTERNAL_FORMAT_FILE_MASK);
while (dir_iter.next(file))
@ -367,7 +376,7 @@ void FSFloaterPoser::onClickPoseSave()
bool successfulSave = savePoseToXml(avatar, filename);
if (successfulSave)
{
refreshPosesScroll();
refreshPoseScroll(POSER_AVATAR_SCROLLLIST_LOADSAVE_NAME, POSE_SAVE_SUBDIRECTORY);
setUiSelectedAvatarSaveFileName(filename);
// TODO: provide feedback for save
@ -682,6 +691,97 @@ void FSFloaterPoser::onPoseMenuAction(const LLSD &param)
refreshJointScrollListMembers();
}
void FSFloaterPoser::onClickLoadLeftHandPose()
{
// This is a double-click function: it needs to run twice within some amount of time to complete.
auto timeIntervalSinceLastClick = std::chrono::system_clock::now() - _timeLastClickedJointReset;
_timeLastClickedJointReset = std::chrono::system_clock::now();
if (timeIntervalSinceLastClick > _doubleClickInterval)
return;
onClickLoadHandPose(false);
}
void FSFloaterPoser::onClickLoadRightHandPose()
{
// This is a double-click function: it needs to run twice within some amount of time to complete.
auto timeIntervalSinceLastClick = std::chrono::system_clock::now() - _timeLastClickedJointReset;
_timeLastClickedJointReset = std::chrono::system_clock::now();
if (timeIntervalSinceLastClick > _doubleClickInterval)
return;
onClickLoadHandPose(true);
}
void FSFloaterPoser::onClickLoadHandPose(bool isRightHand)
{
LLScrollListCtrl* handPosesScrollList = getChild<LLScrollListCtrl>(POSER_AVATAR_SCROLLLIST_HAND_PRESETS_NAME);
if (!handPosesScrollList)
return;
LLScrollListItem* item = handPosesScrollList->getFirstSelected();
if (!item)
return;
std::string poseName = item->getColumn(0)->getValue().asString();
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_PRESETS_HANDS_SUBDIRECTORY);
if (!gDirUtilp->fileExists(pathname))
return;
std::string fullPath =
gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_PRESETS_HANDS_SUBDIRECTORY, poseName + POSE_INTERNAL_FORMAT_FILE_EXT);
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!_poserAnimator.isPosingAvatar(avatar))
return;
try
{
LLSD pose;
llifstream infile;
LLVector3 vec3;
infile.open(fullPath);
if (!infile.is_open())
return;
while (!infile.eof())
{
S32 lineCount = LLSDSerialize::fromXML(pose, infile);
if (lineCount == LLSDParser::PARSE_FAILURE)
{
LL_WARNS("Posing") << "Failed to parse loading a file for a hand: " << poseName << LL_ENDL;
return;
}
for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr)
{
std::string const& name = itr->first;
LLSD const& control_map = itr->second;
if (name.find("Hand") == std::string::npos)
continue;
if (isRightHand != (name.find("Left") == std::string::npos))
continue;
const FSPoserAnimator::FSPoserJoint* poserJoint = _poserAnimator.getPoserJointByName(name);
if (!poserJoint)
continue;
vec3.setValue(control_map["rotation"]);
_poserAnimator.setJointRotation(avatar, poserJoint, vec3, NONE, SWAP_NOTHING, NEGATE_NOTHING);
}
}
}
catch (...)
{
LL_WARNS("Posing") << "Threw an exception trying to load a hand pose: " << poseName << LL_ENDL;
}
}
void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, std::string poseFileName, E_LoadPoseMethods loadMethod)
{
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY);
@ -698,8 +798,9 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, std::string poseFileNam
loadMethod == ROT_POS_AND_SCALES;
bool loadPositions = loadMethod == POSITIONS || loadMethod == ROTATIONS_AND_POSITIONS || loadMethod == POSITIONS_AND_SCALES ||
loadMethod == ROT_POS_AND_SCALES;
bool loadScales = loadMethod == SCALES || loadMethod == POSITIONS_AND_SCALES || loadMethod == ROTATIONS_AND_SCALES ||
loadMethod == ROT_POS_AND_SCALES;
bool loadScales = loadMethod == SCALES || loadMethod == POSITIONS_AND_SCALES || loadMethod == ROTATIONS_AND_SCALES ||
loadMethod == ROT_POS_AND_SCALES;
bool loadHandsOnly = loadMethod == HAND_RIGHT || loadMethod == HAND_LEFT;
try
{
@ -726,10 +827,21 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, std::string poseFileNam
std::string const &name = itr->first;
LLSD const &control_map = itr->second;
if (loadHandsOnly && name.find("Hand") == std::string::npos)
continue;
const FSPoserAnimator::FSPoserJoint *poserJoint = _poserAnimator.getPoserJointByName(name);
if (!poserJoint)
continue;
if (loadHandsOnly && control_map.has("rotation"))
{
vec3.setValue(control_map["rotation"]);
_poserAnimator.setJointRotation(avatar, poserJoint, vec3, NONE, SWAP_NOTHING, NEGATE_NOTHING);
continue;
}
if (loadRotations && control_map.has("rotation"))
{
vec3.setValue(control_map["rotation"]);
@ -1016,7 +1128,7 @@ void FSFloaterPoser::onToggleLoadSavePanel()
this->reshape(poserFloaterWidth, poserFloaterHeight);
if (loadSavePanelExpanded)
refreshPosesScroll();
refreshPoseScroll(POSER_AVATAR_SCROLLLIST_LOADSAVE_NAME, POSE_SAVE_SUBDIRECTORY);
showOrHideAdvancedSaveOptions();
}
@ -1067,6 +1179,8 @@ void FSFloaterPoser::setRotationChangeButtons(bool togglingMirror, bool toggling
if (togglingMirror || togglingSympathetic)
deltaModeToggleButton->setValue(false);
refreshTrackpadCursor();
}
void FSFloaterPoser::onUndoLastRotation()
@ -1346,11 +1460,11 @@ std::vector<FSPoserAnimator::FSPoserJoint *> FSFloaterPoser::getUiSelectedPoserJ
{
std::vector<FSPoserAnimator::FSPoserJoint *> joints;
LLTabContainer *jointTabGroup = getChild<LLTabContainer>(POSER_AVATAR_TABGROUP_JOINTS);
if (!jointTabGroup)
LLTabContainer *tabGroup = getChild<LLTabContainer>(POSER_AVATAR_TABGROUP_JOINTS);
if (!tabGroup)
return joints;
std::string activeTabName = jointTabGroup->getCurrentPanel()->getName();
std::string activeTabName = tabGroup->getCurrentPanel()->getName();
if (activeTabName.empty())
return joints;
@ -1363,7 +1477,18 @@ std::vector<FSPoserAnimator::FSPoserJoint *> FSFloaterPoser::getUiSelectedPoserJ
else if (boost::iequals(activeTabName, POSER_AVATAR_TAB_FACE))
scrollListName = POSER_AVATAR_SCROLLLIST_FACEJOINTS_NAME;
else if (boost::iequals(activeTabName, POSER_AVATAR_TAB_HANDS))
scrollListName = POSER_AVATAR_SCROLLLIST_HANDJOINTS_NAME;
{
tabGroup = getChild<LLTabContainer>(POSER_AVATAR_TAB_HANDS);
if (!tabGroup)
return joints;
activeTabName = tabGroup->getCurrentPanel()->getName();
if (activeTabName.empty())
return joints;
if (boost::iequals(activeTabName, POSER_AVATAR_TAB_HANDJOINTS))
scrollListName = POSER_AVATAR_SCROLLLIST_HANDJOINTS_NAME;
}
else if (boost::iequals(activeTabName, POSER_AVATAR_TAB_MISC))
scrollListName = POSER_AVATAR_SCROLLLIST_MISCJOINTS_NAME;
else if (boost::iequals(activeTabName, POSER_AVATAR_TAB_VOLUMES))
@ -2018,7 +2143,6 @@ uuid_vec_t FSFloaterPoser::getCurrentlyListedAvatarsAndAnimeshes()
S32 FSFloaterPoser::getAvatarListIndexForUuid(LLUUID toFind)
{
LLScrollListCtrl* avatarScrollList = getChild<LLScrollListCtrl>(POSER_AVATAR_SCROLLLIST_AVATARSELECTION);
if (!avatarScrollList)
return -1;

View File

@ -38,11 +38,14 @@ typedef enum E_LoadPoseMethods
{
ROTATIONS = 1,
POSITIONS = 2,
SCALES = 4,
ROTATIONS_AND_POSITIONS = 3,
ROTATIONS_AND_SCALES = 4,
POSITIONS_AND_SCALES = 5,
ROT_POS_AND_SCALES = 6
SCALES = 3,
ROTATIONS_AND_POSITIONS = 4,
ROTATIONS_AND_SCALES = 5,
POSITIONS_AND_SCALES = 6,
ROT_POS_AND_SCALES = 7,
HAND_RIGHT = 8,
HAND_LEFT = 9,
FACE_ONLY = 10,
} E_LoadPoseMethods;
/// <summary>
@ -79,9 +82,9 @@ class FSFloaterPoser : public LLFloater
const F32 normalTrackpadRangeInRads = F_PI;
/// <summary>
/// Refreshes the pose load/save list.
/// Refreshes the supplied pose list from the supplued subdirectory.
/// </summary>
void refreshPosesScroll();
void refreshPoseScroll(std::string scrollListName, std::string subDirectory);
/// <summary>
/// (Dis)Enables all of the posing controls; such as when you can't pose for reasons.
@ -227,6 +230,9 @@ class FSFloaterPoser : public LLFloater
void onPoseJointsReset();
void onOpenSetAdvancedPanel();
void onAdjustTrackpadSensitivity();
void onClickLoadLeftHandPose();
void onClickLoadRightHandPose();
void onClickLoadHandPose(bool isRightHand);
// UI Refreshments
void refreshRotationSliders();

View File

@ -521,38 +521,109 @@ width="565">
relative_width="0.9" />
</scroll_list>
</panel>
<panel
<tab_container
follows="all"
background_visible="false"
height="299"
halign="center"
height="285"
layout="topleft"
left="0"
enabled="true"
title="Hands"
name="hands_joints_panel"
name="hands_tabs"
tab_height="20"
tab_width="50"
tab_group="1"
tab_position="top"
top="0"
width="481">
<scroll_list
column_padding="2"
draw_heading="true"
height="300"
follows="all"
can_sort="false"
layout="topleft"
left="2"
width="479"
multi_select="true"
name="hand_joints_scroll"
top="0">
<scroll_list.columns
label=""
name="icon"
relative_width="0.1" />
<scroll_list.columns
label="Body Part"
name="joint"
relative_width="0.9" />
</scroll_list>
</panel>
width="235">
<panel
follows="all"
background_visible="false"
height="299"
layout="topleft"
left="0"
title="Presets"
name="hands_presets_panel"
top="0"
width="481">
<scroll_list
column_padding="2"
draw_heading="true"
height="275"
follows="all"
can_sort="false"
layout="topleft"
left="2"
width="479"
multi_select="false"
name="hand_presets_scroll"
top="0">
<scroll_list.columns
label="Preset Name"
name="name"
relative_width="0.9" />
</scroll_list>
<button
follows="top|left"
height="21"
layout="topleft"
label="Set Left"
name="button_loadHandPoseLeft"
tool_tip="Double click to set your left hand to the selected preset"
top_delta="238"
left_pad="1"
left="2"
width="88" >
<button.commit_callback
function="Poser.LoadLeftHand"/>
</button>
<button
follows="left|top"
height="21"
layout="topleft"
label="Set Right"
name="button_loadHandPoseRight"
tool_tip="Double click to set your right hand to the selected preset"
left_pad="1"
top_delta="0"
width="88" >
<button.commit_callback
function="Poser.LoadRightHand"/>
</button>
</panel>
<panel
follows="all"
background_visible="false"
height="299"
layout="topleft"
left="0"
title="Adjust"
name="hands_joints_panel"
top="0"
width="481">
<scroll_list
column_padding="2"
draw_heading="true"
height="300"
follows="all"
can_sort="false"
layout="topleft"
left="2"
width="479"
multi_select="true"
name="hand_joints_scroll"
top="0">
<scroll_list.columns
label=""
name="icon"
relative_width="0.1" />
<scroll_list.columns
label="Body Part"
name="joint"
relative_width="0.9" />
</scroll_list>
</panel>
</tab_container>
<panel
follows="all"
background_visible="false"