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. 【vue2】Style和Class,条件,列表渲染,双向数据绑定,事件处理

    目录 1.style和class 2. 条件渲染 2.1 指令 2.2 案例 3. 列表渲染 3.1 v-for:放在标签上,可以循环显示多个此标签 3.2 v-for 循环数组,循环字符串,数字,对 ...

  2. html小总结(哪些可以直接设置高度和宽度)

    (1)当然块级元素是可以直接设置高度和宽度的 块级元素:块级大多为结构性标记 div.h1~h6.ul.ol.dl.form.table.p.hr.pre.address.center.blockqu ...

  3. PCA降维的原理及实现

    PCA可以将数据从原来的向量空间映射到新的空间中.由于每次选择的都是方差最大的方向,所以往往经过前几个维度的划分后,之后的数据排列都非常紧密了, 我们可以舍弃这些维度从而实现降维 原理 内积 两个向量 ...

  4. selenium 添加特殊配置(如不完整 希望各位大神评论告诉我)

    options 常用配置 #添加特殊配置 options=webdriver.ChromeOptions() #设置默认编码为utf-8,也就是中文 options.add_argument('lan ...

  5. 将Oracle数据库迁移到达梦数据库

    公司某产品在项目现场上常用到的数据库有Oracle和达梦. 做性能测试需要根据项目现场预埋大量的基础数据和业务数据,耗费时间.精力.故完成Oracle数据库的性能测试之后,采用直接将Oracle数据库 ...

  6. JetBrains新产品Aqua——自动化测试开发工具(抢鲜体验)

    转载请注明出处️ 作者:测试蔡坨坨 原文链接:caituotuo.top/9a093c88.html 你好,我是测试蔡坨坨. 随着行业内卷越来越严重,自动化测试已成为测试工程师的必备技能,谈及自动化测 ...

  7. 【笔记】CF1607F Robot on the Board 2 及相关

    题目传送门 记忆化搜索 首先,这题 \(10000\) 组 \(2000\times 2000\) 的数据直接爆搜肯定会超时.想到,如果一个点的答案已经被更新过,之后走到这个点能再多走的点也就确定了, ...

  8. C#11之原始字符串

    最近.NET7.0和C#11相继发布,笔者也是第一时间就用上了C#11,其中C#11的有一个更新能解决困扰我多年的问题,也就是文章的标题原始字符串. 在使用C#11的原始字符串时,发现的一些有意思的东 ...

  9. Go实现常用软件设计模式三:生成器模式

    目录: 举个栗子 概念介绍 使用场景 1.举个栗子 2.概念介绍 使用一个中间件来帮助我们填充创建对象参数 优点: 将创建逻辑集中在一起 复用了不同参数创建逻辑 缺点: 新增生成器类 3.使用场景 m ...

  10. Spring Cloud Gateway 使用示例

    Spring Cloud Gateway 使用示例 作者: Grey 原文地址: 博客园:Spring Cloud Gateway 使用示例 CSDN:Spring Cloud Gateway 使用示 ...