1、工程创建

我们使用Visual Studio 2022开发,把下载好后的PotreeDesktop源码添加到Visual Studio中。

打开Visual Studio 2022,新建Asp.Net Core空项目,如下图所示。

点击下一步按钮,设置项目的名称、存储路径以及解决方案名称等。如下图所示。

点击下一步按钮,最后点击创建按钮,完成创建工作。创建后的根目录如下图所示。

打开WOBM.Potree.Web目录,里面的内容如下图所示。

创建的工程在Visual Studio中的工程树如下图所示。

接下来,我们把PotreeDesktop下的源码,拷贝到WOBM.Potree.Web目录下,拷贝后,文件夹组织如下图所示。

从Visual Studio的工程树上,通过添加、删除和项目中排除等方法,最终工程树的组织如下图所示。

目录上不需要的内容,也可以删除,最终保留的内容如下图所示。

此时,双击PotreeDesktop.bat,如果系统能够成功启动,那么开发环境就算搭建成功了,接下来就可以在Visual Studio写我们自定义的html和js代码了。

2、系统启动过程

系统是从PotreeDesktop.bat开始启动的,里面的内容如下所示。

start ./node_modules/electron/dist/electron.exe ./main

系统从electron.exe启动,后面跟了一个参数,参数启动了main.js文件。在main.js中有下面一句代码。

mainWindow.loadFile(path.join(__dirname, 'index.html'));

该代码的意思,是在mainWindow中加载index.html文件,所以index.html页面是我们展示的主页面。打开index.html页面,我们可以看到,该页面就是我们平常开发的Web页面,把这个页面单独放到Web服务器,然后通过浏览器访问也是可以的。

在index.html页面中,我们看到了熟悉的代码,例如对其他js库的引用,html元素的定义和一些js代码。js代码中,定义了Potree.Viewer,也就是显示点云的主UI。

整个执行流程如下,通过PotreeDesktop.bat启动electron.exe,electron.exe加载解析main.js文件,在main.js文件中设置加载index.html页面,至此完成了我们开发的主页面的加载。

3、Main.js文件内容介绍

在该文件的开始,先定义了一些变量,代码如下。

const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const Menu = electron.Menu;
const MenuItem = electron.MenuItem;
const remote = electron.remote;
const path = require('path')
const url = require('url')
let mainWindow

通过这段代码,获取了对electron的引用,app为系统的主App,BrowserWindow是主对话框,Menu是主对话框中的菜单。接下来对app进行操作。

app.on('ready', createWindow)
app.on('window-all-closed', function () {
if(process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if(mainWindow === null) {
createWindow()
}
})

当app准备好之后,系统会调用createWindow函数。当所有的窗口关闭后,调用app的quit函数。当app被激活的时候,如果当前的mainWindow为null的话,调用createWindow函数。

下面我们顺藤摸瓜,看下定义在main.js中的createWindow函数。代码如下。

function createWindow () {
mainWindow= new BrowserWindow({
width:1600,
height:1200,
webPreferences:{
nodeIntegration:true,
backgroundThrottling:false,
}
})
mainWindow.loadFile(path.join(__dirname,'index.html'));
lettemplate = [
{
label:"Window",
submenu:[
{label:"Reload", click() {mainWindow.webContents.reloadIgnoringCache() }},
{label:"Toggle Developer Tools", click() {mainWindow.webContents.toggleDevTools() }},
]
}
];
letmenu = Menu.buildFromTemplate(template);
mainWindow.setMenu(menu);
{
const{ ipcMain } = require('electron');
ipcMain.on('asynchronous-message',(event, arg) => {
console.log(arg)// prints "ping"
event.reply('asynchronous-reply','pong')
})
ipcMain.on('synchronous-message',(event, arg) => {
console.log(arg)// prints "ping"
event.returnValue= 'pong'
})
}
mainWindow.on('closed',function () {
mainWindow= null
})
}

在该代码中,系统初始化了一个BrowserWindow对象,也就是初始化一个主对话框,设置了宽度为1600,宽度为1200。调用mainWindow.loadFile函数,加载Index.html页面。下面的代码,就是组织主菜单上的菜单,我们开发的时候,一般不用electron上定义的菜单,而直接在Index.html中的菜单。所以实际开发的时候,会把这段代码去掉。

最后是监听mainWindow的closed事件,当窗体关闭后,把主变量mainWindow设置为null。

4、Index.html文件内容介绍

在该文件的Head部分,系统引用了一些css,代码如下所示。

<link rel="stylesheet" type="text/css" href="./libs/potree/potree.css">
<link rel="stylesheet" type="text/css" href="./libs/jquery-ui/jquery-ui.min.css">
<link rel="stylesheet" type="text/css" href="./libs/openlayers3/ol.css">
<link rel="stylesheet" type="text/css" href="./libs/spectrum/spectrum.css">
<link rel="stylesheet" type="text/css" href="./libs/jstree/themes/mixed/style.css">
<link rel="stylesheet" type="text/css" href="./src/desktop.css">

接下来引用外部的js文件。

<script>
if (typeof module === 'object') {
window.module = module; module = undefined;
}
</script>
<script src="./libs/jquery/jquery-3.1.1.min.js"></script>
<script src="./libs/spectrum/spectrum.js"></script>
<script src="./libs/jquery-ui/jquery-ui.min.js"></script>
<script src="./libs/other/BinaryHeap.js"></script>
<script src="./libs/tween/tween.min.js"></script>
<script src="./libs/d3/d3.js"></script>
<script src="./libs/proj4/proj4.js"></script>
<script src="./libs/openlayers3/ol.js"></script>
<script src="./libs/i18next/i18next.js"></script>
<script src="./libs/jstree/jstree.js"></script>
<script src="./libs/potree/potree.js"></script>
<script src="./libs/plasio/js/laslaz.js"></script>

该页面的html元素定义如下。

<div class="potree_container" style="position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; ">
<div id="potree_render_area"></div>
<div id="potree_sidebar_container"></div>
</div>

其中potree_render_area用来放主渲染UI对象Viewer,potree_sidebar_container用来放左侧的功能面板。

let elRenderArea = document.getElementById("potree_render_area");
let viewerArgs = {
noDragAndDrop: true
};
window.viewer = new Potree.Viewer(elRenderArea, viewerArgs);
viewer.setEDLEnabled(true);
viewer.setFOV(60);
viewer.setPointBudget(3 * 1000 * 1000);
viewer.setMinNodeSize(0);
viewer.loadSettingsFromURL();
viewer.setDescription("");
viewer.loadGUI(() => {
viewer.setLanguage('en');
$("#menu_appearance").next().show();
$("#menu_tools").next().show();
$("#menu_scene").next().show();
$("#menu_filters").next().show();
viewer.toggleSidebar();
});

系统启动后,整个系统的主界面就出来了,如下图所示。

左侧为功能面板区,是potree_sidebar_container对应的区域,右侧为点云主显示区,是potree_render_area对应的区域。系统启动后,我们会发现,可以通过把las文件拖到界面上的方式,处理和加载点云数据。这个功能是在desktop.js中实现的。

5、desktop.js文件内容介绍

在index.html中定义了和拖动las文件相关的代码,如下所示。

import {
loadDroppedPointcloud,
createPlaceholder,
convert_17,
convert_20,
doConversion,
dragEnter, dragOver, dragLeave, dropHandler,
} from "./src/desktop.js";
const shell = require('electron').shell;
$(document).on('click', 'a[href^="http"]', function (event) {
event.preventDefault();
shell.openExternal(this.href);
});
let elBody = document.body;
elBody.addEventListener("dragenter", dragEnter, false);
elBody.addEventListener("dragover", dragOver, false);
elBody.addEventListener("dragleave", dragLeave, false);
elBody.addEventListener("drop", dropHandler, false);

从desktop.js文件引用 dragEnter,dragOver, dragLeave, dropHandler等模块。然后在document.body上注册事件,在dragenter的时候,调用desktop.js定义的dragEnter模块,dragover的时候,调用dragOver函数,dragleave的时候,调用dragleave函数,触发drop事件的时候,调用dropHandler函数。

下面我们再看下desktop.js文件中,关于这几个函数和模块的定义。

export function dragEnter(e) {
e.dataTransfer.dropEffect = 'copy';
e.preventDefault();
e.stopPropagation();
console.log("enter");
showDropzones();
return false;
}
export function dragOver(e){
e.preventDefault();
e.stopPropagation();
showDropzones();
return false;
}
export function dragLeave(e){
e.preventDefault();
e.stopPropagation();
hideDropzones();
return false;
}

这三个函数定义的比较简单,当拖到主区域的时候,显示拖拽区域,当移动到主区域的时候,也显示拖拽区域,当离开的时候,则隐藏该区域。显示拖拽区域的效果如下图所示。

当鼠标放下之后,系统会调用dropHandler函数,该函数的定义如下。

export async function dropHandler(event) {
event.preventDefault();
event.stopPropagation();
hideDropzones();
let u = event.clientX / document.body.clientWidth;
console.log(u); const cloudJsFiles = [];
const lasLazFiles = [];
let suggestedDirectory = null;
let suggestedName = null; for (let i = 0; i < event.dataTransfer.items.length; i++) {
let item = event.dataTransfer.items[i];
if (item.kind !== "file") {
continue;
}
let file = item.getAsFile();
let path = file.path;
const fs = require("fs");
const fsp = fs.promises;
const np = require('path');
const whitelist = [".las", ".laz"];
let isFile = fs.lstatSync(path).isFile(); if (isFile && path.indexOf("cloud.js") >= 0) {
cloudJsFiles.push(file.path);
} else if (isFile && path.indexOf("metadata.json") >= 0) {
cloudJsFiles.push(file.path);
} else if (isFile) {
const extension = np.extname(path).toLowerCase(); if (whitelist.includes(extension)) {
lasLazFiles.push(file.path); if (suggestedDirectory == null) {
suggestedDirectory = np.normalize(`${path}/..`);
suggestedName = np.basename(path, np.extname(path)) + "_converted";
}
}
} else if (fs.lstatSync(path).isDirectory()) {
console.log("start readdir!");
const files = await fsp.readdir(path);
console.log("readdir done!");
for (const file of files) {
const extension = np.extname(file).toLowerCase();
if (whitelist.includes(extension)) {
lasLazFiles.push(`${path}/${file}`);
if (suggestedDirectory == null) {
suggestedDirectory = np.normalize(`${path}/..`);
suggestedName = np.basename(path, np.extname(path)) + "_converted";
}
} else if (file.toLowerCase().endsWith("cloud.js")) {
cloudJsFiles.push(`${path}/${file}`);
} else if (file.toLowerCase().endsWith("metadata.json")) {
cloudJsFiles.push(`${path}/${file}`);
}
};
}
}
if (lasLazFiles.length > 0) {
doConversion(lasLazFiles, suggestedDirectory, suggestedName);
}
for (const cloudjs of cloudJsFiles) {
loadDroppedPointcloud(cloudjs);
}
return false;
};

该代码中,判断拖入系统的内容,可能是文件或者目录,系统需要取出拖入内容包含的cloud.js文件、metadata.json文件或者.las和.laz文件。如果是cloud.js和metadata.json文件,则不需要进行转换,直接调用loadDroppedPointcloud函数加载即可。如果文件是.las和.laz文件,则调用doConversion函数。

export function loadDroppedPointcloud(cloudjsPath) {
const folderName = cloudjsPath.replace(/\\/g, "/").split("/").reverse()[1];
Potree.loadPointCloud(cloudjsPath).then(e => {
let pointcloud = e.pointcloud;
let material = pointcloud.material;
pointcloud.name = folderName;
viewer.scene.addPointCloud(pointcloud);
let hasRGBA = pointcloud.getAttributes().attributes.find(a => a.name === "rgba") !== undefined
if (hasRGBA) {
pointcloud.material.activeAttributeName = "rgba";
} else {
pointcloud.material.activeAttributeName = "color";
}
material.size = 1;
material.pointSizeType = Potree.PointSizeType.ADAPTIVE;
viewer.zoomTo(e.pointcloud);
});
};

获取点云文件路径后,调用Potree.loadPointCloud函数,加载点云数据。该函数执行后,获取pointcloud,系统调用viewer.scene.addPointCloud函数,把打开的点云数据加载到场景中。后面的代码是判断该点云是否包含RGBA属性,如果包含,则使用rgba方式显示点云,如果不包含,则使用单颜色模式显示。

代码最后,设置点显示大小,大小模式并把场景缩放至该点云数据。

当文件是.las和.laz时候,调用doConversion函数,该函数会判断当前用户选择的是使用1.7版本的转换程序,还是使用2.0版本的转换程序,选择不同,调用desktop.js中定义的不同转换函数,以2.0为例,其定义代码如下所示。

export function convert_20(inputPaths, chosenPath, pointcloudName) {
viewer.postMessage(message, { duration: 15000 });
const { spawn, fork, execFile } = require('child_process');
let exe = './libs/PotreeConverter2/PotreeConverter.exe';
let parameters = [
...inputPaths,
"-o", chosenPath
];
const converter = spawn(exe, parameters);
let placeholder = null;
let outputBuffer = "";
converter.stdout.on('data', (data) => {
const string = new TextDecoder("utf-8").decode(data);
console.log("stdout", string);
});
converter.stderr.on('data', (data) => {
console.log("==");
console.error(`stderr: ${data}`);
});
converter.on('exit', (code) => {
console.log(`child process exited with code ${code}`);
const cloudJS = `${chosenPath}/metadata.json`;
console.log("now loading point cloud: " + cloudJS);
let message = `conversion finished, now loading ${cloudJS}`;
viewer.postMessage(message, { duration: 15000 });
Potree.loadPointCloud(cloudJS).then(e => {
let pointcloud = e.pointcloud;
let material = pointcloud.material;
pointcloud.name = pointcloudName;
let hasRGBA = pointcloud.getAttributes().attributes.find(a => a.name === "rgba") !== undefined
if (hasRGBA) {
pointcloud.material.activeAttributeName = "rgba";
} else {
pointcloud.material.activeAttributeName = "color";
}
material.size = 1;
material.pointSizeType = Potree.PointSizeType.ADAPTIVE;
viewer.scene.addPointCloud(pointcloud);
viewer.zoomTo(e.pointcloud);
});
});
}

系统通过spawn调用PotreeConverter.exe,并传入组织好的参数,参数包括输入文件和输出文件路径等。在spawn执行的过程中,可以通过converter.stdout.on('data', (data)捕捉进度信息,通过converter.stderr.on('data', (data)捕捉错误信息,通过converter.on('exit', (code)捕捉执行结束事件。当执行结束后,转换后的结果会包含metadata.json文件,然后调用Potree.loadPointCloud函数加载该文件即可,加载的过程和上面提到的直接加载点云数据一致。

Potree 002 Desktop开发环境搭建的更多相关文章

  1. Ubuntu Desktop开发生产环境搭建

    Ubuntu Desktop开发生产环境搭建 1   开发生产环境搭建 在本节内容开始前,先定义一下使用场合,没有哪种系统或者设备是万能的,都有它的优点和缺点,能够在具体的使用场景,根据自身的需求来取 ...

  2. Cocos2dx-3.0版本 从开发环境搭建(Win32)到项目移植Android平台过程详解

    作为重量级的跨平台开发的游戏引擎,Cocos2d-x在现今的手游开发领域占有重要地位.那么问题来了,作为Cocos2dx的学习者,它的可移植特性我们就需要掌握,要不然总觉得少一门技能.然而这个时候各种 ...

  3. 【OpenStack】OpenStack系列1之OpenStack本地开发环境搭建&&向社区贡献代码

    加入OpenStack社区 https://launchpad.net/,注册用户(597092663@qq.com/Admin@123) 修改个人信息,配置SSH keys.OpenPGP keys ...

  4. 我的开发环境搭建(ubuntu菜鸟)

    前段时间把系统换成了ubuntu,经过一段时间到发展,终于可以比较正常到完成开发工作了,但是就在今天,我的系统崩了,进不了桌面,而且终端里边到中文也显示乱码,尝试了网上说到各种方法无效,最终我决定重装 ...

  5. vue开发环境搭建及热更新

    写这篇博客的目的是让广大的学者在初入Vue项目的时候少走些弯路,虽然现在有很多博客也有差不多的内容,但是博主在里面添加了一些学习时碰到的小问题.在阅读这篇博客之前,我先给大家推荐一篇文章<入门W ...

  6. 【JAVA零基础入门系列】Day1 开发环境搭建

    [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...

  7. Sublime Text 3下C/C++开发环境搭建

    Sublime Text 3下C/C++开发环境搭建 之前在Linux Mint 17一周使用体验中简单介绍过Sublime Text. 1.Sublime Text 3安装 Ubuntu.Linux ...

  8. Weex开发之路(1):开发环境搭建

    一.Weex介绍 Weex是阿里巴巴在2016年6月份对外开源的一款移动端跨平台的移动开发工具,Weex的出现让我们的应用既有了Native的性能和H5的动态性,只要通过前端JS语法就能写出同时兼容i ...

  9. ubuntu-10.10嵌入式开发环境搭建【转】

    本文转载自:http://blog.csdn.net/zjhsucceed_329/article/details/8036781 版权声明:本文为博主原创文章,未经博主允许不得转载. ubuntu- ...

  10. MSYS2开发环境搭建(无幻的博客,编译OpenSSL,可使用pacman升级)

    MSYS2开发环境搭建 软件安装 下载msys2-x86_64软件包,双击安装到某根目录下,比如D:\msys64. pacman是MSYS2自带的软件管理工具: 可通过修改msys64\etc\pa ...

随机推荐

  1. POJ2104 K-th number (整体二分)

    刚学了整体二分,用这种解法来解决这道题. 首先对于每个询问时可以二分解决的,这也是可以使用整体二分的前提.将原来的序列看成是插入操作,和询问操作和在一起根据值域进行二分.用树状数组来检验二分值. 1 ...

  2. Linux-->开关机+用户管理指令

    关机与重启指令 shutdown关机 语法: shutdown -h 关机时间 now 立刻 1 1分种后 shutdown重启 语法: shutdown -r 重启时间 now 立刻 1 1分钟后 ...

  3. 如何用Virtualbox搭建一个虚拟机

    序言 各位好啊,我是会编程的蜗牛,作为java开发者,我们肯定会接触Linux服务器,除了使用云服务搭建Linux服务器外,我们一般也可以在自己的电脑上安装虚拟机来搭建Linux服务器用于各种功能的验 ...

  4. Mysql索引(究极无敌细节版)

    参考了: https://www.jianshu.com/p/ace3cd6526c4 推荐up主https://space.bilibili.com/377905911 推荐书籍<mysql是 ...

  5. Java I/O(4):AIO和NIO中的Selector

    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 在Java NIO的三大核心中,除了Channel和Buffer,剩下的就是Selector了.有的地方叫它选择器,也有叫多路复用器的(比如Ne ...

  6. python字典推导&&列表推导&&输出随机数

    字典推导: x = ['A', 'B', 'C', 'D'] y = ['Alice', 'Bob', 'Cecil', 'David'] print({i:j for i,j in zip(x,y) ...

  7. nrf52——DFU升级OTA升级方式详解(基于SDK开发例程)

    在我们开始前,默认你已经安装好了一些基础工具,如nrfutil,如果你没有安装过请根据官方中文博客去安装好这些基础工具,连接如下:Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片 ...

  8. Java集合精选常见面试题

    前言 博主只是这篇文章的搬运工,为了加强记忆自己梳理了一遍并扩展了部分内容. 集合拓展链接:集合概述&集合之Collection接口 - 至安 - 博客园 (cnblogs.com) Java ...

  9. 第2-1-4章 SpringBoot整合FastDFS文件存储服务

    目录 5 SpringBoot整合 5.1 操作步骤 5.2 项目依赖 5.3 客户端开发 5.3.1 FastDFS配置 5.3.2 FastDFS配置类 5.3.3 文件工具类 5.3.4 文件上 ...

  10. onps栈使用说明(2)——ping、域名解析等网络工具测试

    1. ping测试 协议栈提供ping工具,其头文件为"net_tools/ping.h",将其include进你的目标系统中即可使用这个工具. -- #include " ...