mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-16 15:54:25 +02:00
Add import/export/paste model functionality to editModels.js
This commit is contained in:
parent
2003dc41df
commit
874b542c0c
7 changed files with 510 additions and 5 deletions
|
@ -61,6 +61,398 @@ var jointList = MyAvatar.getJointNames();
|
|||
|
||||
var mode = 0;
|
||||
|
||||
var exportMenu = null;
|
||||
|
||||
var ExportMenu = function(opts) {
|
||||
var self = this;
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 };
|
||||
|
||||
this._onClose = opts.onClose || function() {};
|
||||
this._position = { x: 0.0, y: 0.0, z: 0.0 };
|
||||
this._scale = 1.0;
|
||||
|
||||
var minScale = 1;
|
||||
var maxScale = 32768;
|
||||
var titleWidth = 120;
|
||||
var locationWidth = 100;
|
||||
var scaleWidth = 144;
|
||||
var exportWidth = 100;
|
||||
var cancelWidth = 100;
|
||||
var margin = 4;
|
||||
var height = 30;
|
||||
var outerHeight = height + (2 * margin);
|
||||
var buttonColor = { red: 128, green: 128, blue: 128};
|
||||
|
||||
var SCALE_MINUS = scaleWidth * 40.0 / 100.0;
|
||||
var SCALE_PLUS = scaleWidth * 63.0 / 100.0;
|
||||
|
||||
var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin);
|
||||
var offset = fullWidth / 2;
|
||||
pos.x -= offset;
|
||||
|
||||
var background= Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
opacity: 1,
|
||||
width: fullWidth,
|
||||
height: outerHeight,
|
||||
backgroundColor: { red: 200, green: 200, blue: 200 },
|
||||
text: "",
|
||||
});
|
||||
|
||||
var titleText = Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y - height,
|
||||
font: { size: 14 },
|
||||
width: titleWidth,
|
||||
height: height,
|
||||
backgroundColor: { red: 255, green: 255, blue: 255 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "Export Models"
|
||||
});
|
||||
|
||||
var locationButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin,
|
||||
y: pos.y + margin,
|
||||
width: locationWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "0, 0, 0",
|
||||
});
|
||||
var scaleOverlay = Overlays.addOverlay("image", {
|
||||
x: pos.x + margin + locationWidth,
|
||||
y: pos.y + margin,
|
||||
width: scaleWidth,
|
||||
height: height,
|
||||
subImage: { x: 0, y: 3, width: 144, height: height},
|
||||
imageURL: toolIconUrl + "voxel-size-selector.svg",
|
||||
alpha: 0.9,
|
||||
});
|
||||
var scaleViewWidth = 40;
|
||||
var scaleView = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + SCALE_MINUS,
|
||||
y: pos.y + margin,
|
||||
width: scaleViewWidth,
|
||||
height: height,
|
||||
alpha: 0.0,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "1"
|
||||
});
|
||||
var exportButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + scaleWidth,
|
||||
y: pos.y + margin,
|
||||
width: exportWidth,
|
||||
height: height,
|
||||
color: { red: 0, green: 255, blue: 255 },
|
||||
text: "Export"
|
||||
});
|
||||
var cancelButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + scaleWidth + exportWidth,
|
||||
y: pos.y + margin,
|
||||
width: cancelWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "Cancel"
|
||||
});
|
||||
|
||||
var voxelPreview = Overlays.addOverlay("cube", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
size: this._scale,
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
alpha: 1,
|
||||
solid: false,
|
||||
visible: true,
|
||||
lineWidth: 4
|
||||
});
|
||||
|
||||
this.parsePosition = function(str) {
|
||||
var parts = str.split(',');
|
||||
if (parts.length == 3) {
|
||||
var x = parseFloat(parts[0]);
|
||||
var y = parseFloat(parts[1]);
|
||||
var z = parseFloat(parts[2]);
|
||||
if (isFinite(x) && isFinite(y) && isFinite(z)) {
|
||||
return { x: x, y: y, z: z };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.showPositionPrompt = function() {
|
||||
var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z;
|
||||
while (1) {
|
||||
positionStr = Window.prompt("Position to export form:", positionStr);
|
||||
if (positionStr == null) {
|
||||
break;
|
||||
}
|
||||
print("position: " + position);
|
||||
var position = self.parsePosition(positionStr);
|
||||
if (position != null) {
|
||||
self.setPosition(position.x, position.y, position.z);
|
||||
break;
|
||||
}
|
||||
Window.alert("The position you entered was invalid.");
|
||||
}
|
||||
};
|
||||
|
||||
this.setScale = function(scale) {
|
||||
self._scale = Math.min(maxScale, Math.max(minScale, scale));
|
||||
Overlays.editOverlay(scaleView, { text: self._scale });
|
||||
Overlays.editOverlay(voxelPreview, { size: self._scale });
|
||||
}
|
||||
|
||||
this.decreaseScale = function() {
|
||||
self.setScale(self._scale /= 2);
|
||||
}
|
||||
|
||||
this.increaseScale = function() {
|
||||
self.setScale(self._scale *= 2);
|
||||
}
|
||||
|
||||
this.exportModels = function() {
|
||||
var x = self._position.x;
|
||||
var y = self._position.y;
|
||||
var z = self._position.z;
|
||||
var s = self._scale;
|
||||
var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo";
|
||||
filename = Window.save("Select where to save", filename, "*.svo")
|
||||
Clipboard.exportModels(filename, x, y, z, s);
|
||||
self.close();
|
||||
};
|
||||
|
||||
this.getPosition = function() {
|
||||
return self._position;
|
||||
};
|
||||
|
||||
this.setPosition = function(x, y, z) {
|
||||
self._position = { x: x, y: y, z: z };
|
||||
var positionStr = x + ", " + y + ", " + z;
|
||||
Overlays.editOverlay(locationButton, { text: positionStr });
|
||||
Overlays.editOverlay(voxelPreview, { position: self._position });
|
||||
|
||||
};
|
||||
|
||||
this.mousePressEvent = function(event) {
|
||||
print("Mouse press");
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
};
|
||||
|
||||
this.mouseReleaseEvent = function(event) {
|
||||
print("Mouse release");
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (clickedOverlay == locationButton) {
|
||||
self.showPositionPrompt();
|
||||
} else if (clickedOverlay == exportButton) {
|
||||
self.exportModels();
|
||||
} else if (clickedOverlay == cancelButton) {
|
||||
self.close();
|
||||
} else if (clickedOverlay == scaleOverlay) {
|
||||
var x = event.x - pos.x - margin - locationWidth;
|
||||
print(x);
|
||||
if (x < SCALE_MINUS) {
|
||||
self.decreaseScale();
|
||||
} else if (x > SCALE_PLUS) {
|
||||
self.increaseScale();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.close = function() {
|
||||
this.cleanup();
|
||||
this._onClose();
|
||||
};
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(background);
|
||||
Overlays.deleteOverlay(titleText);
|
||||
Overlays.deleteOverlay(locationButton);
|
||||
Overlays.deleteOverlay(exportButton);
|
||||
Overlays.deleteOverlay(cancelButton);
|
||||
Overlays.deleteOverlay(voxelPreview);
|
||||
Overlays.deleteOverlay(scaleOverlay);
|
||||
Overlays.deleteOverlay(scaleView);
|
||||
};
|
||||
|
||||
print("CONNECTING!");
|
||||
Controller.mousePressEvent.connect(this.mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
|
||||
};
|
||||
|
||||
var ModelImporter = function(opts) {
|
||||
var self = this;
|
||||
|
||||
var height = 30;
|
||||
var margin = 4;
|
||||
var outerHeight = height + (2 * margin);
|
||||
var titleWidth = 120;
|
||||
var cancelWidth = 100;
|
||||
var fullWidth = titleWidth + cancelWidth + (2 * margin);
|
||||
|
||||
// TODO: Show import preview
|
||||
var importBoundaries = Overlays.addOverlay("cube", {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
size: 1,
|
||||
color: { red: 128, blue: 128, green: 128 },
|
||||
lineWidth: 4,
|
||||
solid: false,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 };
|
||||
|
||||
var background= Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
opacity: 1,
|
||||
width: fullWidth,
|
||||
height: outerHeight,
|
||||
backgroundColor: { red: 200, green: 200, blue: 200 },
|
||||
visible: false,
|
||||
text: "",
|
||||
});
|
||||
|
||||
var titleText = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin,
|
||||
y: pos.y + margin,
|
||||
font: { size: 14 },
|
||||
width: titleWidth,
|
||||
height: height,
|
||||
backgroundColor: { red: 255, green: 255, blue: 255 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
visible: false,
|
||||
text: "Import Models"
|
||||
});
|
||||
var cancelButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + titleWidth,
|
||||
y: pos.y + margin,
|
||||
width: cancelWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
visible: false,
|
||||
text: "Cancel"
|
||||
});
|
||||
this._importing = false;
|
||||
|
||||
this.setImportVisible = function(visible) {
|
||||
Overlays.editOverlay(importBoundaries, { visible: visible });
|
||||
Overlays.editOverlay(cancelButton, { visible: visible });
|
||||
Overlays.editOverlay(titleText, { visible: visible });
|
||||
Overlays.editOverlay(background, { visible: visible });
|
||||
};
|
||||
|
||||
var importPosition = { x: 0, y: 0, z: 0 };
|
||||
this.moveImport = function(position) {
|
||||
importPosition = position;
|
||||
// TODO: Show import preview
|
||||
Overlays.editOverlay(importBoundaries, {
|
||||
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
|
||||
});
|
||||
}
|
||||
|
||||
this.mouseMoveEvent = function(event) {
|
||||
if (self._importing) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
|
||||
var distance = 2;// * self._scale;
|
||||
|
||||
if (false) {//intersection.intersects) {
|
||||
var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection));
|
||||
if (intersectionDistance < distance) {
|
||||
distance = intersectionDistance * 0.99;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var targetPosition = {
|
||||
x: pickRay.origin.x + (pickRay.direction.x * distance),
|
||||
y: pickRay.origin.y + (pickRay.direction.y * distance),
|
||||
z: pickRay.origin.z + (pickRay.direction.z * distance)
|
||||
};
|
||||
|
||||
if (targetPosition.x < 0) targetPosition.x = 0;
|
||||
if (targetPosition.y < 0) targetPosition.y = 0;
|
||||
if (targetPosition.z < 0) targetPosition.z = 0;
|
||||
|
||||
var nudgeFactor = 1;
|
||||
var newPosition = {
|
||||
x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor,
|
||||
y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor,
|
||||
z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor
|
||||
}
|
||||
|
||||
self.moveImport(newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseReleaseEvent = function(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (clickedOverlay == cancelButton) {
|
||||
self._importing = false;
|
||||
self.setImportVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Would prefer to use {4} for the coords, but it would only capture the last digit.
|
||||
var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/;
|
||||
this.doImport = function() {
|
||||
print("IMPORTING ");
|
||||
if (!self._importing) {
|
||||
var filename = Window.browse("Select models to import", "", "*.svo")
|
||||
if (filename) {
|
||||
parts = fileRegex.exec(filename);
|
||||
if (parts == null) {
|
||||
Window.alert("The file you selected does not contain source domain or location information");
|
||||
} else {
|
||||
var hostname = parts[1];
|
||||
var x = parts[2];
|
||||
var y = parts[3];
|
||||
var z = parts[4];
|
||||
var s = parts[5];
|
||||
if (hostname != location.hostname) {
|
||||
if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Window.confirm(("Would you like to import back to the source location?"))) {
|
||||
Window.alert("(TODO) Importing backing to source location");
|
||||
Clipboard.importModels(filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Clipboard.importModels(filename);
|
||||
self._importing = true;
|
||||
self.setImportVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.paste = function() {
|
||||
if (self._importing) {
|
||||
self._importing = false;
|
||||
self.setImportVisible(false);
|
||||
Clipboard.pasteModels();
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(importBoundaries);
|
||||
Overlays.deleteOverlay(cancelButton);
|
||||
Overlays.deleteOverlay(titleText);
|
||||
Overlays.deleteOverlay(background);
|
||||
}
|
||||
|
||||
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(this.mouseMoveEvent);
|
||||
};
|
||||
|
||||
var modelImporter = new ModelImporter();
|
||||
|
||||
function isLocked(properties) {
|
||||
// special case to lock the ground plane model in hq.
|
||||
if (location.hostname == "hq.highfidelity.io" &&
|
||||
|
@ -1045,7 +1437,7 @@ function mouseReleaseEvent(event) {
|
|||
var modelMenuAddedDelete = false;
|
||||
function setupModelMenus() {
|
||||
print("setupModelMenus()");
|
||||
// add our menuitems
|
||||
// adj our menuitems
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
|
||||
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
|
||||
|
@ -1057,6 +1449,12 @@ function setupModelMenus() {
|
|||
} else {
|
||||
print("delete exists... don't add ours");
|
||||
}
|
||||
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
|
||||
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
|
||||
}
|
||||
|
||||
function cleanupModelMenus() {
|
||||
|
@ -1066,6 +1464,12 @@ function cleanupModelMenus() {
|
|||
// delete our menuitems
|
||||
Menu.removeMenuItem("Edit", "Delete");
|
||||
}
|
||||
|
||||
Menu.removeMenuItem("Edit", "Paste Models");
|
||||
|
||||
Menu.removeSeparator("File", "Models");
|
||||
Menu.removeMenuItem("File", "Export Models");
|
||||
Menu.removeMenuItem("File", "Import Models");
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
|
@ -1074,6 +1478,10 @@ function scriptEnding() {
|
|||
toolBar.cleanup();
|
||||
cleanupModelMenus();
|
||||
tooltip.cleanup();
|
||||
modelImporter.cleanup();
|
||||
if (exportMenu) {
|
||||
exportMenu.close();
|
||||
}
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
@ -1128,6 +1536,21 @@ function handeMenuEvent(menuItem){
|
|||
Models.editModel(editModelID, properties);
|
||||
}
|
||||
}
|
||||
} else if (menuItem == "Paste Models") {
|
||||
modelImporter.paste();
|
||||
} else if (menuItem == "Export Models") {
|
||||
if (!exportMenu) {
|
||||
// if (modelImporter) {
|
||||
// modelImporter.close();
|
||||
// }
|
||||
exportMenu = new ExportMenu({
|
||||
onClose: function() {
|
||||
exportMenu = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (menuItem == "Import Models") {
|
||||
modelImporter.doImport();
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
|
@ -1173,4 +1596,4 @@ Controller.keyReleaseEvent.connect(function(event) {
|
|||
if (event.text == "BACKSPACE") {
|
||||
handeMenuEvent("Delete");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -141,6 +141,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_voxelImporter(NULL),
|
||||
_importSucceded(false),
|
||||
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
|
||||
_modelClipboardRenderer(),
|
||||
_modelClipboard(),
|
||||
_wantToKillLocalVoxels(false),
|
||||
_viewFrustum(),
|
||||
_lastQueriedViewFrustum(),
|
||||
|
@ -1483,6 +1485,21 @@ struct SendVoxelsOperationArgs {
|
|||
const unsigned char* newBaseOctCode;
|
||||
};
|
||||
|
||||
void Application::exportModels(const QString& filename, float x, float y, float z, float scale) {
|
||||
ModelTreeElement* selectedNode = _models.getTree()->getModelAt(x, y, z, scale);
|
||||
if (selectedNode) {
|
||||
qDebug() << "Exporting models doing it!" << filename;
|
||||
ModelTree exportTree;
|
||||
_models.getTree()->copySubTreeIntoNewTree(selectedNode, &exportTree, true);
|
||||
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
|
||||
} else {
|
||||
qDebug() << "No models were selected";
|
||||
}
|
||||
|
||||
// restore the main window's active state
|
||||
_window->activateWindow();
|
||||
}
|
||||
|
||||
bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
|
||||
|
@ -1564,6 +1581,20 @@ void Application::importVoxels() {
|
|||
emit importDone();
|
||||
}
|
||||
|
||||
void Application::importModels(const QString& filename) {
|
||||
_importSucceded = false;
|
||||
|
||||
|
||||
_models.getTree()->readFromSVOFile(filename.toLocal8Bit().constData());
|
||||
_models.getTree()->reaverageOctreeElements();
|
||||
|
||||
|
||||
// restore the main window's active state
|
||||
_window->activateWindow();
|
||||
|
||||
emit importDone();
|
||||
}
|
||||
|
||||
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
|
||||
copyVoxels(sourceVoxel);
|
||||
deleteVoxelAt(sourceVoxel);
|
||||
|
@ -1719,6 +1750,10 @@ void Application::init() {
|
|||
_models.init();
|
||||
_models.setViewFrustum(getViewFrustum());
|
||||
|
||||
_modelClipboardRenderer.init();
|
||||
_modelClipboardRenderer.setViewFrustum(getViewFrustum());
|
||||
_modelClipboardRenderer.setTree(&_modelClipboard);
|
||||
|
||||
_metavoxels.init();
|
||||
|
||||
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
|
||||
|
@ -2083,6 +2118,7 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("idle/update/_models");
|
||||
_models.update(); // update the models...
|
||||
_modelClipboardRenderer.update();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -2566,6 +2602,7 @@ void Application::updateShadowMap() {
|
|||
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
|
||||
_particles.render(OctreeRenderer::SHADOW_RENDER_MODE);
|
||||
_models.render(OctreeRenderer::SHADOW_RENDER_MODE);
|
||||
_modelClipboardRenderer.render(OctreeRenderer::SHADOW_RENDER_MODE);
|
||||
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
|
||||
|
@ -2767,6 +2804,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... models...");
|
||||
_models.render();
|
||||
_modelClipboardRenderer.render();
|
||||
}
|
||||
|
||||
// render the ambient occlusion effect if enabled
|
||||
|
|
|
@ -308,6 +308,9 @@ public slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
void packetSent(quint64 length);
|
||||
|
||||
void exportModels(const QString& filename, float x, float y, float z, float scale);
|
||||
void importModels(const QString& filename);
|
||||
|
||||
void importVoxels(); // doesn't include source voxel because it goes to clipboard
|
||||
void cutVoxels(const VoxelDetail& sourceVoxel);
|
||||
void copyVoxels(const VoxelDetail& sourceVoxel);
|
||||
|
@ -452,6 +455,8 @@ private:
|
|||
ParticleCollisionSystem _particleCollisionSystem;
|
||||
|
||||
ModelTreeRenderer _models;
|
||||
ModelTreeRenderer _modelClipboardRenderer;
|
||||
ModelTree _modelClipboard;
|
||||
|
||||
QByteArray _voxelsFilename;
|
||||
bool _wantToKillLocalVoxels;
|
||||
|
|
|
@ -100,3 +100,16 @@ void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s,
|
|||
|
||||
Application::getInstance()->nudgeVoxelsByVector(sourceVoxel, nudgeVecInTreeSpace);
|
||||
}
|
||||
|
||||
|
||||
void ClipboardScriptingInterface::exportModels(const QString& filename, float x, float y, float z, float s) {
|
||||
Application::getInstance()->exportModels(filename, x / TREE_SCALE, y / TREE_SCALE, z / TREE_SCALE, s / TREE_SCALE);
|
||||
}
|
||||
|
||||
void ClipboardScriptingInterface::importModels(const QString& filename) {
|
||||
Application::getInstance()->importModels(filename);
|
||||
}
|
||||
|
||||
void ClipboardScriptingInterface::pasteModels() {
|
||||
// TODO
|
||||
}
|
||||
|
|
|
@ -42,6 +42,10 @@ public slots:
|
|||
|
||||
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
|
||||
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
|
||||
|
||||
void importModels(const QString& filename);
|
||||
void exportModels(const QString& filename, float x, float y, float z, float s);
|
||||
void pasteModels();
|
||||
};
|
||||
|
||||
#endif // hifi_ClipboardScriptingInterface_h
|
||||
|
|
|
@ -53,6 +53,15 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
|
|||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal),
|
||||
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter),
|
||||
Q_ARG(QFileDialog::AcceptMode, QFileDialog::AcceptSave));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Display an alert box
|
||||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue::UndefinedValue
|
||||
|
@ -93,18 +102,29 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const
|
|||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
|
||||
QFileDialog::AcceptMode acceptMode) {
|
||||
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
|
||||
// filename if the directory is valid.
|
||||
QString path = "";
|
||||
QFileInfo fileInfo = QFileInfo(directory);
|
||||
qDebug() << "File: " << directory << fileInfo.isFile();
|
||||
if (fileInfo.isDir()) {
|
||||
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
|
||||
path = fileInfo.filePath();
|
||||
}
|
||||
|
||||
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
|
||||
fileDialog.setFileMode(QFileDialog::ExistingFile);
|
||||
// fileDialog.setFileMode(QFileDialog::ExistingFile);
|
||||
fileDialog.setAcceptMode(acceptMode);
|
||||
qDebug() << "Opening!";
|
||||
QUrl fileUrl(directory);
|
||||
if (acceptMode == QFileDialog::AcceptSave) {
|
||||
fileDialog.setFileMode(QFileDialog::Directory);
|
||||
fileDialog.selectFile(fileUrl.fileName());
|
||||
// qDebug() << "Setting filename!";
|
||||
// fileDialog.setLabelText(QFileDialog::FileName, fileUrl.fileName());
|
||||
}
|
||||
if (fileDialog.exec()) {
|
||||
return QScriptValue(fileDialog.selectedFiles().first());
|
||||
}
|
||||
|
|
|
@ -31,12 +31,14 @@ public slots:
|
|||
QScriptValue confirm(const QString& message = "");
|
||||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
private slots:
|
||||
QScriptValue showAlert(const QString& message);
|
||||
QScriptValue showConfirm(const QString& message);
|
||||
QScriptValue showPrompt(const QString& message, const QString& defaultText);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
|
||||
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
|
||||
};
|
||||
|
||||
#endif // hifi_WindowScriptingInterface_h
|
||||
|
|
Loading…
Reference in a new issue