Save/load potree measurements

pull/999/head
Piero Toffanin 2021-06-11 14:22:53 -04:00
rodzic cf554fdbfd
commit 8f760ecd7f
3 zmienionych plików z 169 dodań i 4 usunięć

Wyświetl plik

@ -1,5 +1,7 @@
from .tasks import TaskNestedView from .tasks import TaskNestedView
from .common import get_and_check_project
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import exceptions
class Scene(TaskNestedView): class Scene(TaskNestedView):
def get(self, request, pk=None, project_pk=None): def get(self, request, pk=None, project_pk=None):
@ -9,3 +11,46 @@ class Scene(TaskNestedView):
task = self.get_and_check_task(request, pk) task = self.get_and_check_task(request, pk)
return Response(task.potree_scene) return Response(task.potree_scene)
def post(self, request, pk=None, project_pk=None):
"""
Store potree scene information (except camera view)
"""
get_and_check_project(request, project_pk, perms=("change_project", ))
task = self.get_and_check_task(request, pk)
scene = request.data
# Quick type check
if scene.get('type') != 'Potree':
raise exceptions.ValidationError(detail="Invalid potree scene")
for k in scene:
if not k in ["view", "pointclouds", "settings"]:
task.potree_scene[k] = scene[k]
task.save()
return Response({'success': True})
class CameraView(TaskNestedView):
def post(self, request, pk=None, project_pk=None):
"""
Store camera view information
"""
get_and_check_project(request, project_pk, perms=("change_project", ))
task = self.get_and_check_task(request, pk)
view = request.data
if not view:
raise exceptions.ValidationError(detail="view parameter missing")
if not task.potree_scene:
init_p = {
'type': 'Potree',
'version': 1.7
}
task.potree_scene = init_p
task.potree_scene['view'] = view
task.save()
return Response({'success': True})

Wyświetl plik

@ -10,7 +10,7 @@ from .admin import UserViewSet, GroupViewSet
from rest_framework_nested import routers from rest_framework_nested import routers
from rest_framework_jwt.views import obtain_jwt_token from rest_framework_jwt.views import obtain_jwt_token
from .tiler import TileJson, Bounds, Metadata, Tiles, Export from .tiler import TileJson, Bounds, Metadata, Tiles, Export
from .potree import Scene from .potree import Scene, CameraView
from .workers import CheckTask, GetTaskResult from .workers import CheckTask, GetTaskResult
router = routers.DefaultRouter() router = routers.DefaultRouter()
@ -47,6 +47,7 @@ urlpatterns = [
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/download/(?P<image_filename>.+)$', ImageDownload.as_view()), url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/download/(?P<image_filename>.+)$', ImageDownload.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/3d/scene$', Scene.as_view()), url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/3d/scene$', Scene.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/3d/cameraview$', CameraView.as_view()),
url(r'workers/check/(?P<celery_task_id>.+)', CheckTask.as_view()), url(r'workers/check/(?P<celery_task_id>.+)', CheckTask.as_view()),
url(r'workers/get/(?P<celery_task_id>.+)', GetTaskResult.as_view()), url(r'workers/get/(?P<celery_task_id>.+)', GetTaskResult.as_view()),

Wyświetl plik

@ -15,6 +15,57 @@ require('./vendor/OBJLoader');
require('./vendor/MTLLoader'); require('./vendor/MTLLoader');
require('./vendor/ColladaLoader'); require('./vendor/ColladaLoader');
class SetCameraView extends React.Component{
static propTypes = {
viewer: PropTypes.object.isRequired,
task: PropTypes.object.isRequired
}
constructor(props){
super(props);
this.state = {
error: "",
showOk: false
}
}
handleClick = () => {
const { view } = Potree.saveProject(this.props.viewer);
const showError = () => {
this.setState({error: _("Cannot set initial camera view")});
setTimeout(() => this.setState({error: ""}), 3000);
};
const showOk = () => {
this.setState({showOk: true});
setTimeout(() => this.setState({showOk: false}), 2000);
}
$.ajax({
url: `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/3d/cameraview`,
contentType: 'application/json',
data: JSON.stringify(view),
dataType: 'json',
type: 'POST'
}).done(result => {
if (result.success) showOk();
else showError();
}).fail(() => {
showError();
});
}
render(){
return ([<input key="btn" type="button" onClick={this.handleClick}
style={{marginBottom: 12, display: 'inline-block'}} name="set_camera_view"
value={_("set initial camera view")} />,
this.state.showOk ? (<div key="ok" style={{color: 'lightgreen', display: 'inline-block', marginLeft: 12}}></div>) : "",
this.state.error ? (<div key="error" style={{color: 'red'}}>{this.state.error}</div>) : ""
]
);
}
}
class TexturedModelMenu extends React.Component{ class TexturedModelMenu extends React.Component{
static propTypes = { static propTypes = {
toggleTexturedModel: PropTypes.func.isRequired toggleTexturedModel: PropTypes.func.isRequired
@ -221,6 +272,17 @@ class ModelView extends React.Component {
}); });
} }
getSceneData(){
let json = Potree.saveProject(window.viewer);
// Remove view, settings since we don't want to trigger
// scene updates when these change.
delete json.view;
delete json.settings;
return json;
}
componentDidMount() { componentDidMount() {
let container = this.container; let container = this.container;
if (!container) return; // Enzyme tests don't have support for all WebGL methods so we just skip this if (!container) return; // Enzyme tests don't have support for all WebGL methods so we just skip this
@ -250,6 +312,10 @@ class ModelView extends React.Component {
$("#cameras").hide(); $("#cameras").hide();
$("#cameras_container").hide(); $("#cameras_container").hide();
} }
const $scv = $("<div id='set-camera-view'></div>");
$scv.prependTo($("#scene_export").parent());
window.ReactDOM.render(<SetCameraView viewer={viewer} task={this.props.task} />, $scv.get(0));
}); });
viewer.scene.scene.add( new THREE.AmbientLight( 0x404040, 2.0 ) ); // soft white light ); viewer.scene.scene.add( new THREE.AmbientLight( 0x404040, 2.0 ) ); // soft white light );
@ -280,7 +346,60 @@ class ModelView extends React.Component {
type: "GET", type: "GET",
url: `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/3d/scene` url: `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/3d/scene`
}).done(sceneData => { }).done(sceneData => {
let localSceneData = Potree.saveProject(viewer);
// Check if we do not have a view set
// if so, just keep the current view information
if (!sceneData.view || !sceneData.view.position){
sceneData.view = localSceneData.view;
}
sceneData.pointclouds = localSceneData.pointclouds;
sceneData.settings = localSceneData.settings;
// Load
Potree.loadProject(viewer, sceneData); Potree.loadProject(viewer, sceneData);
// Every 3 seconds, check if the scene has changed
// if it has, save the changes server-side
// Unfortunately Potree does not have reliable events
// for trivially detecting changes in measurements
let saveSceneReq = null;
let saveSceneInterval = null;
let saveSceneErrors = 0;
let prevSceneData = JSON.stringify(this.getSceneData());
const postSceneData = (sceneData) => {
if (saveSceneReq){
saveSceneReq.abort();
saveSceneReq = null;
}
saveSceneReq = $.ajax({
url: `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/3d/scene`,
contentType: 'application/json',
data: sceneData,
dataType: 'json',
type: 'POST'
}).done(result => {
if (result.success){
saveSceneErrors = 0;
prevSceneData = sceneData;
}else{
console.warn("Cannot save Potree scene");
}
}).fail(() => {
console.error("Cannot save Potree scene");
if (++saveSceneErrors === 5) clearInterval(saveSceneInterval);
});
};
const checkScene = () => {
const sceneData = JSON.stringify(this.getSceneData());
if (sceneData !== prevSceneData) postSceneData(sceneData);
};
saveSceneInterval = setInterval(checkScene, 3000);
}).fail(e => { }).fail(e => {
console.error("Cannot load 3D scene information", e); console.error("Cannot load 3D scene information", e);
}); });