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. 洛谷P6033 [NOIP2004 提高组] 合并果子 加强版 (单调队列)

    数据加强了,原来nlogn的复杂度就不行了...... 首先对原来的n个数排序(注意不能用快排),因为值域是1e5,所以可以开桶排序,开两个队列,一个存原来的n个数(已经满足单增),另一队列存两两合并 ...

  2. 作用域通信对象:session用户在登录时通过`void setAttribute(String name,Object value)`方法设置用户名和密码。点击登录按钮后,跳转到另外一个页面显示用户

    作用域通信对象:session session对象基于会话,不同用户拥有不同的会话.同一个用户共享session对象的所有属性.作用域开始客户连接到应用程序的某个页面,结束与服务器断开连接.sessi ...

  3. 齐博x1fun实例 鉴于很多人问列表的筛选怎么放到首页、内容页等等地方 贴出方法

    application\common\fun\Field.php 你可以复制一份 也可以直接改 直接改记得加锁 不然升级就覆盖了 我们把   public function list_filter($ ...

  4. 每日算法3:随机生成五个不同整数,将数字转换为RMB格式

    随机生成五个不同整数 点击查看代码 /* 题目解析: 1.采用Math对象的random()方法, 2.将每次生成的数跟之前的数判断相等则此次生成无效i-- */ function randomNum ...

  5. linux清理内存缓存cache

    Linux服务器有自己先进的内存管理机制,有时候会发现我们系统的buff/cache内存占用会越来越高,操作系统也有卡顿的情况,遇到这种情况,不妨试试下面的方法. 1步骤一:我们先查看物理内存占用情况 ...

  6. webRTC demo

    准备: 信令服务 前端页面用于视频通话 demo github 地址. 前端页面 为了使 demo 尽量简单,功能页面如下,即包含登录.通过对方手机号拨打电话的功能.在实际生成过程中,未必使用的手机号 ...

  7. 部署grafana+telegraf+influxdb 及 配置 jmeter后端监听

    搞性能测试,可以搭建Grafana+Telegraf+InfluxDB 监控平台,监控服务器资源使用率.jmeter性能测试结果等. telegraf: 是一个用 Go 编写的代理程序,可收集系统和服 ...

  8. spring源码解析(一) 环境搭建(各种坑的解决办法)

    上次搭建spring源码的环境还是两年前,依稀记得那时候也是一顿折腾,奈何当时没有记录,导致两年后的今天把坑重踩了一遍,还遇到了新的坑,真是欲哭无泪;为了以后类似的事情不再发生,这次写下这篇博文来必坑 ...

  9. 2022-11-08 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...

  10. DHorse系列文章之镜像制作

    DHorse系列文章之镜像制作 制作镜像常用的工具 使用Docker制作镜像 1.使用docker commit制作 该命令使用比较简单,可以自行网上搜索教程. 2.使用Dockerfile制作 这种 ...