mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 00:08:53 +02:00
create projects / style changes
This commit is contained in:
parent
ceb3b6385d
commit
cb74313de8
12 changed files with 693 additions and 131 deletions
|
@ -15,92 +15,160 @@ Windows.ScrollingWindow {
|
||||||
width: 480
|
width: 480
|
||||||
height: 706
|
height: 706
|
||||||
title: "Avatar Packager"
|
title: "Avatar Packager"
|
||||||
resizable: true
|
resizable: false
|
||||||
opacity: parent.opacity
|
opacity: parent.opacity
|
||||||
destroyOnHidden: true
|
destroyOnHidden: true
|
||||||
implicitWidth: 384; implicitHeight: 640
|
implicitWidth: 384; implicitHeight: 640
|
||||||
minSize: Qt.vector2d(200, 300)
|
minSize: Qt.vector2d(480, 706)
|
||||||
|
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
|
||||||
//HifiConstants { id: hifi }
|
|
||||||
Item {
|
Item {
|
||||||
|
id: windowContent
|
||||||
height: pane.height
|
height: pane.height
|
||||||
width: pane.width
|
width: pane.width
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
AvatarProject {
|
// FIXME: modal overlay does not show
|
||||||
id: avatarProject
|
Rectangle {
|
||||||
colorScheme: root.colorScheme
|
id: modalOverlay
|
||||||
visible: false
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
z: 20000
|
||||||
|
color: "#aa031b33"
|
||||||
|
clip: true
|
||||||
|
visible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Column {
|
||||||
id: avatarPackagerMain
|
id: avatarPackager
|
||||||
anchors.left: parent.left
|
anchors.fill: parent
|
||||||
anchors.right: parent.right
|
state: "main"
|
||||||
anchors.top: parent.top
|
states: [
|
||||||
anchors.bottom: parent.bottom
|
State {
|
||||||
RalewaySemiBold {
|
name: "main"
|
||||||
id: avatarPackagerLabel
|
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); faqEnabled: true; backButtonEnabled: false }
|
||||||
size: 24;
|
PropertyChanges { target: avatarPackagerMain; visible: true }
|
||||||
anchors.left: parent.left
|
PropertyChanges { target: avatarPackagerFooter; content: avatarPackagerMain.footer }
|
||||||
anchors.top: parent.top
|
},
|
||||||
anchors.topMargin: 25
|
State {
|
||||||
anchors.bottomMargin: 25
|
name: "createProject"
|
||||||
text: 'Avatar Packager'
|
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Create Project") }
|
||||||
}
|
PropertyChanges { target: createAvatarProject; visible: true }
|
||||||
|
PropertyChanges { target: avatarPackagerFooter; content: createAvatarProject.footer }
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "project"
|
||||||
|
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name }
|
||||||
|
PropertyChanges { target: avatarProject; visible: true }
|
||||||
|
PropertyChanges { target: avatarPackagerFooter; content: avatarProject.footer }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
HifiControls.Button {
|
AvatarPackagerHeader {
|
||||||
id: createProjectButton
|
id: avatarPackagerHeader
|
||||||
anchors.left: parent.left
|
onBackButtonClicked: {
|
||||||
anchors.right: parent.right
|
avatarPackager.state = "main"
|
||||||
anchors.top: avatarPackagerLabel.bottom
|
|
||||||
text: qsTr("Create Project")
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: 30
|
|
||||||
onClicked: function() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HifiControls.Button {
|
|
||||||
id: openProjectButton
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: createProjectButton.bottom
|
|
||||||
text: qsTr("Open Avatar Project")
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: 30
|
|
||||||
onClicked: function() {
|
|
||||||
var avatarProjectsPath = fileDialogHelper.standardPath(/*fileDialogHelper.StandardLocation.DocumentsLocation*/ 1) + "/High Fidelity/Avatar Projects";
|
|
||||||
console.log("path = " + avatarProjectsPath);
|
|
||||||
|
|
||||||
// TODO: make the dialog modal
|
Item {
|
||||||
|
height: pane.height - avatarPackagerHeader.height - avatarPackagerFooter.height
|
||||||
|
width: pane.width
|
||||||
|
|
||||||
var browser = desktop.fileDialog({
|
Rectangle {
|
||||||
selectDirectory: false,
|
anchors.fill: parent
|
||||||
dir: fileDialogHelper.pathToUrl(avatarProjectsPath),
|
color: "#404040"
|
||||||
filter: "Avatar Project FST Files (*.fst)",
|
}
|
||||||
title: "Open Project (.fst)"
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.canceled.connect(function() {
|
AvatarProject {
|
||||||
|
id: avatarProject
|
||||||
});
|
colorScheme: root.colorScheme
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
browser.selectedFile.connect(function(fileUrl) {
|
CreateAvatarProject {
|
||||||
console.log("FOUND PATH " + fileUrl);
|
id: createAvatarProject
|
||||||
let fstFilePath = fileDialogHelper.urlToPath(fileUrl);
|
colorScheme: root.colorScheme
|
||||||
let currentAvatarProject = AvatarPackagerCore.openAvatarProject(fstFilePath);
|
anchors.fill: parent
|
||||||
if (currentAvatarProject) {
|
}
|
||||||
console.log("LOAD COMPLETE");
|
|
||||||
console.log("file dir = " + AvatarPackagerCore.currentAvatarProject.projectFolderPath);
|
Item {
|
||||||
|
id: avatarPackagerMain
|
||||||
avatarPackagerMain.visible = false;
|
visible: false
|
||||||
avatarProject.visible = true;
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property var footer: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 17
|
||||||
|
HifiControls.Button {
|
||||||
|
id: createProjectButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: openProjectButton.left
|
||||||
|
anchors.rightMargin: 22
|
||||||
|
height: 40
|
||||||
|
width: 134
|
||||||
|
text: qsTr("New Project")
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
onClicked: {
|
||||||
|
avatarPackager.state = "createProject";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
HifiControls.Button {
|
||||||
|
id: openProjectButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: 40
|
||||||
|
width: 133
|
||||||
|
text: qsTr("Open Project")
|
||||||
|
color: hifi.buttons.blue
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
onClicked: {
|
||||||
|
// TODO: make the dialog modal
|
||||||
|
let browser = desktop.fileDialog({
|
||||||
|
selectDirectory: false,
|
||||||
|
dir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH),
|
||||||
|
filter: "Avatar Project FST Files (*.fst)",
|
||||||
|
title: "Open Project (.fst)",
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.canceled.connect(function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.selectedFile.connect(function(fileUrl) {
|
||||||
|
let fstFilePath = fileDialogHelper.urlToPath(fileUrl);
|
||||||
|
let currentAvatarProject = AvatarPackagerCore.openAvatarProject(fstFilePath);
|
||||||
|
if (currentAvatarProject) {
|
||||||
|
avatarPackager.state = "project";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Flow {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: 18
|
||||||
|
leftMargin: 16
|
||||||
|
rightMargin: 16
|
||||||
|
}
|
||||||
|
RalewayRegular {
|
||||||
|
size: 20
|
||||||
|
color: "white"
|
||||||
|
text: qsTr("Use a custom avatar to express your identity")
|
||||||
|
}
|
||||||
|
RalewayRegular {
|
||||||
|
size: 20
|
||||||
|
color: "white"
|
||||||
|
text: qsTr("To learn more about using this tool, visit our docs")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AvatarPackagerFooter {
|
||||||
|
id: avatarPackagerFooter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
|
||||||
|
import "../../controlsUit" 1.0 as HifiControls
|
||||||
|
import "../../stylesUit" 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: avatarPackagerFooter
|
||||||
|
|
||||||
|
color: "#404040"
|
||||||
|
height: 74
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
property var content: Item { }
|
||||||
|
|
||||||
|
children: [background, content]
|
||||||
|
|
||||||
|
property var background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#404040"
|
||||||
|
// TODO Use a shadow instead / border is just here for element debug purposes
|
||||||
|
border.width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
|
||||||
|
import "../../controlsUit" 1.0 as HifiControls
|
||||||
|
import "../../stylesUit" 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: avatarPackagerHeader
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 74
|
||||||
|
color: "#252525"
|
||||||
|
|
||||||
|
property alias title: title.text
|
||||||
|
property alias faqEnabled: faq.visible
|
||||||
|
property alias backButtonEnabled: back.visible
|
||||||
|
signal backButtonClicked
|
||||||
|
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: back
|
||||||
|
visible: true
|
||||||
|
size: 28
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.verticalCenter: back.verticalCenter
|
||||||
|
text: "◀"
|
||||||
|
color: "white"
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: avatarPackagerHeader.backButtonClicked()
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: { state = "hovering" }
|
||||||
|
onExited: { state = "" }
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "hovering"
|
||||||
|
PropertyChanges {
|
||||||
|
target: back
|
||||||
|
color: "gray"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: title
|
||||||
|
size: 28
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: back.visible ? back.right : parent.left
|
||||||
|
anchors.leftMargin: back.visible ? 11 : 21
|
||||||
|
anchors.verticalCenter: title.verticalCenter
|
||||||
|
text: qsTr("Avatar Packager")
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: faq
|
||||||
|
visible: false
|
||||||
|
size: 28
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
anchors.verticalCenter: faq.verticalCenter
|
||||||
|
text: qsTr("FAQ")
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,36 +11,56 @@ Item {
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
|
|
||||||
property int colorScheme;
|
property int colorScheme
|
||||||
|
|
||||||
visible: true
|
|
||||||
|
|
||||||
|
visible: false
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
|
|
||||||
|
property var footer: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 17
|
||||||
|
HifiControls.Button {
|
||||||
|
id: uploadButton
|
||||||
|
//width: parent.width
|
||||||
|
//anchors.bottom: parent.bottom
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: qsTr("Upload")
|
||||||
|
color: hifi.buttons.blue
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
width: 133
|
||||||
|
height: 40
|
||||||
|
onClicked: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RalewaySemiBold {
|
RalewaySemiBold {
|
||||||
id: avatarProjectLabel
|
id: avatarFBXNameLabel
|
||||||
size: 24;
|
size: 14
|
||||||
width: parent.width
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 25
|
anchors.topMargin: 25
|
||||||
anchors.bottomMargin: 25
|
anchors.bottomMargin: 25
|
||||||
text: 'Avatar Project'
|
text: qsTr("FBX file here")
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
id: openFolderButton
|
id: openFolderButton
|
||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.top: avatarProjectLabel.bottom
|
anchors.top: avatarFBXNameLabel.bottom
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
text: qsTr("Open Project Folder")
|
text: qsTr("Open Project Folder")
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
height: 30
|
height: 30
|
||||||
onClicked: function() {
|
onClicked: {
|
||||||
fileDialogHelper.openDirectory(AvatarPackagerCore.currentAvatarProject.projectFolderPath);
|
fileDialogHelper.openDirectory(fileDialogHelper.pathToUrl(AvatarPackagerCore.currentAvatarProject.projectFolderPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: 'white'
|
color: "white"
|
||||||
visible: AvatarPackagerCore.currentAvatarProject !== null
|
visible: AvatarPackagerCore.currentAvatarProject !== null
|
||||||
anchors.top: openFolderButton.bottom
|
anchors.top: openFolderButton.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -56,15 +76,4 @@ Item {
|
||||||
delegate: Text { text: '<b>File:</b> ' + modelData }
|
delegate: Text { text: '<b>File:</b> ' + modelData }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HifiControls.Button {
|
|
||||||
id: uploadButton
|
|
||||||
width: parent.width
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
text: qsTr("Upload")
|
|
||||||
color: hifi.buttons.blue
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: 30
|
|
||||||
onClicked: function() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import "../../controlsUit" 1.0 as HifiControls
|
||||||
|
import "../../stylesUit" 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
|
||||||
|
property int colorScheme
|
||||||
|
|
||||||
|
property var footer: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 17
|
||||||
|
HifiControls.Button {
|
||||||
|
id: createButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: 30
|
||||||
|
width: 133
|
||||||
|
text: qsTr("Create")
|
||||||
|
onClicked: {
|
||||||
|
if (!AvatarPackagerCore.createAvatarProject(projectLocation.text, name.text, avatarModel.text, textureFolder.text)) {
|
||||||
|
Window.alert('Failed to create project')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
avatarPackager.state = "project";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
anchors.fill: parent
|
||||||
|
height: parent.height
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: createAvatarColumns
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: 10
|
||||||
|
|
||||||
|
spacing: 17
|
||||||
|
|
||||||
|
ProjectInputControl {
|
||||||
|
id: name
|
||||||
|
label: "Name"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectInputControl {
|
||||||
|
id: projectLocation
|
||||||
|
label: "Specify Project Location"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
browseEnabled: true
|
||||||
|
browseFolder: true
|
||||||
|
browseDir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH)
|
||||||
|
browseTitle: "Project Location"
|
||||||
|
text: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH)
|
||||||
|
onTextChanged: {
|
||||||
|
//TODO: valid folder? Does project with name exist here already?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectInputControl {
|
||||||
|
id: avatarModel
|
||||||
|
label: "Specify Avatar Model (.fbx)"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
browseEnabled: true
|
||||||
|
browseFolder: false
|
||||||
|
browseDir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH)
|
||||||
|
browseFilter: "Avatar Model File (*.fbx)"
|
||||||
|
browseTitle: "Open Avatar Model (.fbx)"
|
||||||
|
onTextChanged: {
|
||||||
|
//TODO: try to get texture folder from fbx if none is set?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectInputControl {
|
||||||
|
id: textureFolder
|
||||||
|
label: "Specify Texture Folder"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
browseEnabled: true
|
||||||
|
browseFolder: true
|
||||||
|
browseDir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH)
|
||||||
|
browseTitle: "Texture Folder"
|
||||||
|
onTextChanged: {
|
||||||
|
//TODO: valid folder?
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RalewayRegular {
|
||||||
|
text: "A folder with that name already exists at that location. Please choose a different project name or location."
|
||||||
|
color: "#EA4C5F";
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
size: 20
|
||||||
|
anchors {
|
||||||
|
top: createAvatarColumns.bottom
|
||||||
|
bottom: parent.bottom
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
|
||||||
|
import "../../controlsUit" 1.0 as HifiControls
|
||||||
|
import "../../stylesUit" 1.0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 21
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
|
height: 75
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
property alias label: label.text
|
||||||
|
property alias browseEnabled: browseButton.visible
|
||||||
|
property bool browseFolder: false
|
||||||
|
property string browseFilter: "All Files (*.*)"
|
||||||
|
property string browseTitle: "Open file"
|
||||||
|
property string browseDir: ""
|
||||||
|
property alias text: input.text
|
||||||
|
|
||||||
|
property int colorScheme
|
||||||
|
|
||||||
|
Row {
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: label
|
||||||
|
size: 20
|
||||||
|
font.weight: Font.Medium
|
||||||
|
text: ""
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
width: control.width
|
||||||
|
spacing: 16
|
||||||
|
height: 40
|
||||||
|
HifiControls.TextField {
|
||||||
|
id: input
|
||||||
|
colorScheme: control.colorScheme
|
||||||
|
font.family: "Fira Sans"
|
||||||
|
font.pixelSize: 18
|
||||||
|
height: parent.height
|
||||||
|
width: browseButton.visible ? parent.width - browseButton.width - parent.spacing : parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
HifiControls.Button {
|
||||||
|
id: browseButton
|
||||||
|
visible: false
|
||||||
|
height: parent.height
|
||||||
|
width: 133
|
||||||
|
text: qsTr("Browse")
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
onClicked: {
|
||||||
|
// TODO: make the dialog modal
|
||||||
|
let browser = desktop.fileDialog({
|
||||||
|
selectDirectory: browseFolder,
|
||||||
|
dir: browseDir,
|
||||||
|
filter: browseFilter,
|
||||||
|
title: browseTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.canceled.connect(function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.selectedFile.connect(function(fileUrl) {
|
||||||
|
input.text = fileDialogHelper.urlToPath(fileUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
#include "AvatarPackager.h"
|
#include "AvatarPackager.h"
|
||||||
|
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
#include <QQmlEngine>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <OffscreenUi.h>
|
#include <OffscreenUi.h>
|
||||||
|
@ -25,6 +26,8 @@ std::once_flag setupQMLTypesFlag;
|
||||||
AvatarPackager::AvatarPackager() {
|
AvatarPackager::AvatarPackager() {
|
||||||
std::call_once(setupQMLTypesFlag, []() {
|
std::call_once(setupQMLTypesFlag, []() {
|
||||||
qmlRegisterType<FST>();
|
qmlRegisterType<FST>();
|
||||||
|
qRegisterMetaType<AvatarPackager*>();
|
||||||
|
qRegisterMetaType<AvatarProject*>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,12 +41,24 @@ bool AvatarPackager::open() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* AvatarPackager::openAvatarProject(QString avatarProjectFSTPath) {
|
AvatarProject* AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) {
|
||||||
if (_currentAvatarProject) {
|
if (_currentAvatarProject) {
|
||||||
//_currentAvatarProject->deleteLater();
|
_currentAvatarProject->deleteLater();
|
||||||
//_currentAvatarProject = nullptr;
|
|
||||||
}
|
}
|
||||||
_currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath);
|
_currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath);
|
||||||
|
qDebug() << "_currentAvatarProject has" << (QQmlEngine::objectOwnership(_currentAvatarProject) == QQmlEngine::CppOwnership ? "CPP" : "JS") << "OWNERSHIP";
|
||||||
|
QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership);
|
||||||
|
emit avatarProjectChanged();
|
||||||
|
return _currentAvatarProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarProject* AvatarPackager::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) {
|
||||||
|
if (_currentAvatarProject) {
|
||||||
|
_currentAvatarProject->deleteLater();
|
||||||
|
}
|
||||||
|
_currentAvatarProject = AvatarProject::createAvatarProject(avatarProjectName, avatarModelPath);
|
||||||
|
qDebug() << "_currentAvatarProject has" << (QQmlEngine::objectOwnership(_currentAvatarProject) == QQmlEngine::CppOwnership ? "CPP" : "JS") << "OWNERSHIP";
|
||||||
|
QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership);
|
||||||
emit avatarProjectChanged();
|
emit avatarProjectChanged();
|
||||||
return _currentAvatarProject;
|
return _currentAvatarProject;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,25 +16,28 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
|
#include "FileDialogHelper.h"
|
||||||
|
|
||||||
#include "avatar/AvatarProject.h"
|
#include "avatar/AvatarProject.h"
|
||||||
|
|
||||||
class AvatarPackager : public QObject, public Dependency {
|
class AvatarPackager : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
Q_PROPERTY(QObject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged)
|
Q_PROPERTY(AvatarProject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged)
|
||||||
|
Q_PROPERTY(QString AVATAR_PROJECTS_PATH READ getAvatarProjectsPath CONSTANT)
|
||||||
public:
|
public:
|
||||||
AvatarPackager();
|
AvatarPackager();
|
||||||
bool open();
|
bool open();
|
||||||
|
|
||||||
Q_INVOKABLE QObject* openAvatarProject(QString avatarProjectFSTPath);
|
Q_INVOKABLE AvatarProject* createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder);
|
||||||
|
Q_INVOKABLE AvatarProject* openAvatarProject(const QString& avatarProjectFSTPath);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void avatarProjectChanged();
|
void avatarProjectChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; };
|
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; };
|
||||||
//Q_INVOKABLE QObject* openAvatarProject();
|
Q_INVOKABLE QString getAvatarProjectsPath() const { return AvatarProject::getDefaultProjectsPath(); }
|
||||||
Q_INVOKABLE QObject* uploadItem();
|
Q_INVOKABLE QObject* uploadItem();
|
||||||
|
|
||||||
AvatarProject* _currentAvatarProject{ nullptr };
|
AvatarProject* _currentAvatarProject{ nullptr };
|
||||||
|
|
|
@ -15,44 +15,93 @@
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
#include "FBXSerializer.h"
|
||||||
|
|
||||||
AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
|
AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
|
||||||
const auto pathToLower = path.toLower();
|
if (!path.toLower().endsWith(".fst")) {
|
||||||
if (pathToLower.endsWith(".fst")) {
|
return nullptr;
|
||||||
QFile file{ path };
|
}
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
QFile file{ path };
|
||||||
return nullptr;
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
}
|
return nullptr;
|
||||||
auto project = new AvatarProject(path, file.readAll());
|
}
|
||||||
QQmlEngine::setObjectOwnership(project, QQmlEngine::CppOwnership);
|
const auto project = new AvatarProject(path, file.readAll());
|
||||||
return project;
|
QQmlEngine::setObjectOwnership(project, QQmlEngine::CppOwnership);
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarProject* AvatarProject::createAvatarProject(const QString& avatarProjectName, const QString& avatarModelPath) {
|
||||||
|
if (!isValidNewProjectName(avatarProjectName)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
QDir dir(getDefaultProjectsPath() + "/" + avatarProjectName);
|
||||||
|
if (!dir.mkpath(".")) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const auto fileName = QFileInfo(avatarModelPath).fileName();
|
||||||
|
const auto newModelPath = dir.absoluteFilePath(fileName);
|
||||||
|
const auto newFSTPath = dir.absoluteFilePath("avatar.fst");
|
||||||
|
QFile::copy(avatarModelPath, newModelPath);
|
||||||
|
|
||||||
|
QFileInfo fbxInfo(newModelPath);
|
||||||
|
QFile fbx(fbxInfo.filePath());
|
||||||
|
if (!fbxInfo.exists() || !fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) {
|
||||||
|
// TODO: Can't open model FBX (throw error here)
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathToLower.endsWith(".fbx")) {
|
std::shared_ptr<hfm::Model> hfmModel;
|
||||||
// TODO: Create FST here:
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
qDebug() << "Reading FBX file : " << fbxInfo.filePath();
|
||||||
|
const QByteArray fbxContents = fbx.readAll();
|
||||||
|
hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), fbxInfo.filePath());
|
||||||
|
}
|
||||||
|
catch (const QString& error) {
|
||||||
|
qDebug() << "Error reading: " << error;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
//TODO: copy/fix textures here:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FST* fst = FST::createFSTFromModel(newFSTPath, newModelPath, *hfmModel);
|
||||||
|
|
||||||
|
fst->setName(avatarProjectName);
|
||||||
|
|
||||||
|
if (!fst->write()) {
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return new AvatarProject(fst);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarProject::isValidNewProjectName(const QString& projectName) {
|
||||||
|
QDir dir(getDefaultProjectsPath() + "/" + projectName);
|
||||||
|
return !dir.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) :
|
AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) :
|
||||||
_fstPath(fstPath), _fst(fstPath, FSTReader::readMapping(data)) {
|
AvatarProject::AvatarProject(new FST(fstPath, FSTReader::readMapping(data))) {
|
||||||
|
|
||||||
_directory = QFileInfo(_fstPath).absoluteDir();
|
}
|
||||||
|
AvatarProject::AvatarProject(FST* fst) {
|
||||||
|
_fst = fst;
|
||||||
|
auto fileInfo = QFileInfo(getFSTPath());
|
||||||
|
_directory = fileInfo.absoluteDir();
|
||||||
|
|
||||||
//_projectFiles = _directory.entryList();
|
//_projectFiles = _directory.entryList();
|
||||||
refreshProjectFiles();
|
refreshProjectFiles();
|
||||||
|
|
||||||
auto fileInfo = QFileInfo(_fstPath);
|
|
||||||
_projectPath = fileInfo.absoluteDir().absolutePath();
|
_projectPath = fileInfo.absoluteDir().absolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarProject::appendDirectory(QString prefix, QDir dir) {
|
void AvatarProject::appendDirectory(QString prefix, QDir dir) {
|
||||||
qDebug() << "Inside of " << prefix << dir.absolutePath();
|
qDebug() << "Inside of " << prefix << dir.absolutePath();
|
||||||
auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
|
const auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
|
||||||
for (auto& entry : dir.entryInfoList({}, flags)) {
|
for (auto& entry : dir.entryInfoList({}, flags)) {
|
||||||
if (entry.isFile()) {
|
if (entry.isFile()) {
|
||||||
_projectFiles.append(prefix + "/" + entry.fileName());
|
_projectFiles.append(prefix + "/" + entry.fileName());
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QVariantHash>
|
#include <QVariantHash>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
class AvatarProject : public QObject {
|
class AvatarProject : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -31,6 +32,7 @@ class AvatarProject : public QObject {
|
||||||
Q_PROPERTY(QString projectFolderPath READ getProjectPath)
|
Q_PROPERTY(QString projectFolderPath READ getProjectPath)
|
||||||
Q_PROPERTY(QString projectFSTPath READ getFSTPath)
|
Q_PROPERTY(QString projectFSTPath READ getFSTPath)
|
||||||
Q_PROPERTY(QString projectFBXPath READ getFBXPath)
|
Q_PROPERTY(QString projectFBXPath READ getFBXPath)
|
||||||
|
Q_PROPERTY(QString name READ getProjectName)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE bool write() {
|
Q_INVOKABLE bool write() {
|
||||||
|
@ -38,38 +40,41 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE QObject* upload() {
|
|
||||||
// TODO: create new AvatarProjectUploader here, launch it and return it for status tracking in QML
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the AvatarProject or a nullptr on failure.
|
* returns the AvatarProject or a nullptr on failure.
|
||||||
*/
|
*/
|
||||||
static AvatarProject* openAvatarProject(const QString& path);
|
static AvatarProject* openAvatarProject(const QString& path);
|
||||||
|
static AvatarProject* createAvatarProject(const QString& avatarProjectName, const QString& avatarModelPath);
|
||||||
|
|
||||||
|
static bool isValidNewProjectName(const QString& projectName);
|
||||||
|
|
||||||
|
static QString getDefaultProjectsPath() {
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/High Fidelity Projects";
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AvatarProject(const QString& fstPath, const QByteArray& data);
|
AvatarProject(const QString& fstPath, const QByteArray& data);
|
||||||
|
AvatarProject(FST* fst);
|
||||||
|
|
||||||
~AvatarProject() {
|
~AvatarProject() {
|
||||||
// TODO: cleanup FST / AvatarProjectUploader etc.
|
// TODO: cleanup FST / AvatarProjectUploader etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE QString getProjectName() const { return _fst->getName(); }
|
||||||
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
|
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
|
||||||
Q_INVOKABLE QString getFSTPath() const { return _fstPath; }
|
Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); }
|
||||||
Q_INVOKABLE QString getFBXPath() const { return _fst.getModelPath(); }
|
Q_INVOKABLE QString getFBXPath() const { return _fst->getModelPath(); }
|
||||||
|
|
||||||
FST* getFST() { return &_fst; }
|
FST* getFST() { return _fst; }
|
||||||
|
|
||||||
void refreshProjectFiles();
|
void refreshProjectFiles();
|
||||||
void appendDirectory(QString prefix, QDir dir);
|
void appendDirectory(QString prefix, QDir dir);
|
||||||
|
|
||||||
FST _fst;
|
FST* _fst;
|
||||||
|
|
||||||
QDir _directory;
|
QDir _directory;
|
||||||
QStringList _projectFiles{};
|
QStringList _projectFiles{};
|
||||||
QString _projectPath;
|
QString _projectPath;
|
||||||
QString _fstPath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarProject_h
|
#endif // hifi_AvatarProject_h
|
||||||
|
|
|
@ -13,21 +13,125 @@
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <hfm/HFM.h>
|
||||||
|
|
||||||
FST::FST(QString fstPath, QVariantHash data) : _fstPath(fstPath) {
|
FST::FST(const QString& fstPath, QVariantHash data) : _fstPath(fstPath) {
|
||||||
if (data.contains("name")) {
|
if (data.contains(NAME_FIELD)) {
|
||||||
_name = data["name"].toString();
|
_name = data[NAME_FIELD].toString();
|
||||||
data.remove("name");
|
data.remove(NAME_FIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.contains("filename")) {
|
if (data.contains(FILENAME_FIELD)) {
|
||||||
_modelPath = data["filename"].toString();
|
_modelPath = data[FILENAME_FIELD].toString();
|
||||||
data.remove("filename");
|
data.remove(FILENAME_FIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
_other = data;
|
_other = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FST* FST::createFSTFromModel(QString fstPath, QString modelFilePath, const hfm::Model& hfmModel) {
|
||||||
|
QVariantHash mapping;
|
||||||
|
|
||||||
|
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
||||||
|
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
||||||
|
bool likelyMixamoFile = hfmModel.applicationName == "mixamo.com" ||
|
||||||
|
(hfmModel.blendshapeChannelNames.contains("BrowsDown_Right") &&
|
||||||
|
hfmModel.blendshapeChannelNames.contains("MouthOpen") &&
|
||||||
|
hfmModel.blendshapeChannelNames.contains("Blink_Left") &&
|
||||||
|
hfmModel.blendshapeChannelNames.contains("Blink_Right") &&
|
||||||
|
hfmModel.blendshapeChannelNames.contains("Squint_Right"));
|
||||||
|
|
||||||
|
mapping.insert(NAME_FIELD, QFileInfo(fstPath).baseName());
|
||||||
|
QDir root(modelFilePath);
|
||||||
|
mapping.insert(FILENAME_FIELD, root.relativeFilePath(fstPath));
|
||||||
|
mapping.insert(TEXDIR_FIELD, "textures");
|
||||||
|
mapping.insert(SCRIPT_FIELD, "scripts");
|
||||||
|
|
||||||
|
// mixamo/autodesk defaults
|
||||||
|
mapping.insert(SCALE_FIELD, 1.0);
|
||||||
|
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||||
|
joints.insert("jointEyeLeft", hfmModel.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" :
|
||||||
|
(hfmModel.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye"));
|
||||||
|
|
||||||
|
joints.insert("jointEyeRight", hfmModel.jointIndices.contains("jointEyeRight") ? "jointEyeRight" :
|
||||||
|
hfmModel.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");
|
||||||
|
|
||||||
|
joints.insert("jointNeck", hfmModel.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
|
||||||
|
joints.insert("jointRoot", "Hips");
|
||||||
|
joints.insert("jointLean", "Spine");
|
||||||
|
joints.insert("jointLeftHand", "LeftHand");
|
||||||
|
joints.insert("jointRightHand", "RightHand");
|
||||||
|
|
||||||
|
const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd";
|
||||||
|
joints.insert("jointHead", hfmModel.jointIndices.contains(topName) ? topName : "Head");
|
||||||
|
|
||||||
|
mapping.insert(JOINT_FIELD, joints);
|
||||||
|
|
||||||
|
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||||
|
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||||
|
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||||
|
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||||
|
|
||||||
|
|
||||||
|
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
|
||||||
|
// then we can add the default mixamo to "faceshift" mappings
|
||||||
|
if (likelyMixamoFile) {
|
||||||
|
QVariantHash blendshapes;
|
||||||
|
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0);
|
||||||
|
blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5);
|
||||||
|
blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5);
|
||||||
|
blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0);
|
||||||
|
blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5);
|
||||||
|
blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7);
|
||||||
|
blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5);
|
||||||
|
blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7);
|
||||||
|
blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7);
|
||||||
|
blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0);
|
||||||
|
blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7);
|
||||||
|
blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7);
|
||||||
|
blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25);
|
||||||
|
blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25);
|
||||||
|
blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0);
|
||||||
|
blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0);
|
||||||
|
blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75);
|
||||||
|
blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75);
|
||||||
|
blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5);
|
||||||
|
blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5);
|
||||||
|
mapping.insert(BLENDSHAPE_FIELD, blendshapes);
|
||||||
|
}
|
||||||
|
return new FST(fstPath, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
QString FST::absoluteModelPath() const {
|
QString FST::absoluteModelPath() const {
|
||||||
QFileInfo fileInfo{ _fstPath };
|
QFileInfo fileInfo{ _fstPath };
|
||||||
QDir dir{ fileInfo.absoluteDir() };
|
QDir dir{ fileInfo.absoluteDir() };
|
||||||
|
@ -42,4 +146,21 @@ void FST::setName(const QString& name) {
|
||||||
void FST::setModelPath(const QString& modelPath) {
|
void FST::setModelPath(const QString& modelPath) {
|
||||||
_modelPath = modelPath;
|
_modelPath = modelPath;
|
||||||
emit modelPathChanged(modelPath);
|
emit modelPathChanged(modelPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantHash FST::getMapping() {
|
||||||
|
QVariantHash mapping;
|
||||||
|
mapping.insertMulti(NAME_FIELD, _name);
|
||||||
|
mapping.insertMulti(FILENAME_FIELD, _modelPath);
|
||||||
|
mapping.unite(_other);
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FST::write() {
|
||||||
|
QFile fst(_fstPath);
|
||||||
|
if (!fst.open(QIODevice::WriteOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fst.write(FSTReader::writeMapping(getMapping()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
|
|
||||||
#include <QVariantHash>
|
#include <QVariantHash>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include "FSTReader.h"
|
||||||
|
|
||||||
|
namespace hfm {
|
||||||
|
class Model;
|
||||||
|
};
|
||||||
|
|
||||||
class FST : public QObject {
|
class FST : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -20,7 +25,9 @@ class FST : public QObject {
|
||||||
Q_PROPERTY(QString modelPath READ getModelPath WRITE setModelPath NOTIFY modelPathChanged)
|
Q_PROPERTY(QString modelPath READ getModelPath WRITE setModelPath NOTIFY modelPathChanged)
|
||||||
Q_PROPERTY(QUuid marketplaceID READ getMarketplaceID)
|
Q_PROPERTY(QUuid marketplaceID READ getMarketplaceID)
|
||||||
public:
|
public:
|
||||||
FST(QString fstPath, QVariantHash data);
|
FST(const QString& fstPath, QVariantHash data);
|
||||||
|
|
||||||
|
static FST* createFSTFromModel(QString fstPath, QString modelFilePath, const hfm::Model& hfmModel);
|
||||||
|
|
||||||
QString absoluteModelPath() const;
|
QString absoluteModelPath() const;
|
||||||
|
|
||||||
|
@ -32,6 +39,12 @@ public:
|
||||||
|
|
||||||
QUuid getMarketplaceID() const { return _marketplaceID; }
|
QUuid getMarketplaceID() const { return _marketplaceID; }
|
||||||
|
|
||||||
|
QString getPath() { return _fstPath; }
|
||||||
|
|
||||||
|
QVariantHash getMapping();
|
||||||
|
|
||||||
|
bool write();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged(const QString& name);
|
void nameChanged(const QString& name);
|
||||||
void modelPathChanged(const QString& modelPath);
|
void modelPathChanged(const QString& modelPath);
|
||||||
|
|
Loading…
Reference in a new issue