在TwinMaker中,展示登录用户当前位置在3D上的方法。(Note: This sentence is already in Chinese, so there is no need for paraphrasing.)

这篇文章是关于什么的?

在这篇文章中,我们将解释两个问题。

    • TwinMaker上で、ログインユーザーごとに違うコンテンツを表示する方法を解説します

 

    TwinMaker上で、リアルタイムな位置情報を、3Dコンテンツの位置に反映させる方法を解説します

在这篇文章中要制作的东西

在这篇文章中,我们将创建以下类似TwinMaker的场景。

    • Aさんがログインすると、Aさんの部屋のルンバの位置に、ルンバの3Dモデルが表示されます

 

    • Bさんがログインすると、Bさんの部屋のルンバの位置に、ルンバの3Dモデルが表示されます

 

    また、注釈に表示される画像と文言が変わります
undefined

在这篇文章中,我们不会涉及到获取位置信息的问题,只是简单地说明一下在3D空间中Roomba的位置会发生变化。

怎么实现呢?

    • Pythonの定期実行で、動的にTwinMakerのシーンをコピーします

 

    ログインユーザーの情報が必要になるので、iot-app-kitでWebアプリ化します

你为什么开心?

    ユーザーAさんにはAさん向けの3Dを、BさんにはBさん向けの3Dだけを見せることができます

所需之物

    • AWSアカウント

 

    • Python3.10

 

    • VSCode

 

    Node.js(記事はv14.15.1で検証していますが、新しいほうがいいです)

首先是准备工作:引入IoT应用开发工具包。

由于TwinMaker只能被拥有AWS账户的开发者在AWS管理控制台上查看,因此它不适合根据登录用户的不同动态变化。我们将使用iot-app-kit来解决这个问题。

物联网应用套件是由AWS发布的一个库。可以将TwinMaker的3D场景、图表等作为React的Web应用程序进行构建。可以创建专门供用户浏览的Web页面。

 

由于物联网应用套件库仍然是一个相对新的库,因此构建过程会有一些困难。即使按照README中的指示操作,有时仍无法运行。请参考以下步骤开始构建。

步骤1:创建一个React项目

# プロジェクトを作りたいフォルダで、reactのプロジェクトを作成します
npx create-react-app app --template typescript 

# プロジェクトができたら、作成したフォルダに移動します
cd app

# 必要な依存ライブラリをインストールします
npm install

步骤2:安装IoT应用套件。

# app-kitをインストールします(※READMEに出ている手順はここまでです)
npm install @iot-app-kit/components 
npm install @iot-app-kit/react-components 
npm install @iot-app-kit/source-iottwinmaker 
npm install @iot-app-kit/scene-composer

# とりあえずエラーが出るので、babelのプラグインをインストールします
npm install @babel/plugin-proposal-private-property-in-object

# sassがないので、sassをインストールします
npm install sass

# ポリフィルが必要なので、react-app-rewiredとポリフィルをインストールします
npm install react-app-rewired --save-dev
npm install node-polyfill-webpack-plugin

第三步:在项目的根目录下,放置适用于react-app-wired的配置文件。

const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");

module.exports = function override(config, env) {
  if (!config.plugins) {
    config.plugins = [];
  }
  config.plugins.push(
    new NodePolyfillPlugin({
      excludeAliases: ["console"],
    })
  );
  config.resolve.alias = {
    "react/jsx-runtime.js": "react/jsx-runtime",
    "react/jsx-dev-runtime.js": "react/jsx-dev-runtime",
  };
  return config;
};

第四步:修改package.json文件中的scripts,以便能够使用react-app-rewired。

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },

步骤5:由于three.js版本不兼容,您需要获取最新版本。

npm install three
npm install @react-three/fiber
npm install @react-three/drei
npm install three-stdlib

第六步:准备就绪后,启动React。

npm run start

目前来看,多半这样就可以了(※截至2023年6月11日)。

6月11日时的版本信息:package.jsonpackage.json
{
“name”: “app”,
“version”: “0.1.0”,
“private”: true,
“dependencies”: {
“@babel/plugin-proposal-private-property-in-object”: “^7.21.11”,
“@iot-app-kit/components”: “^6.2.0”,
“@iot-app-kit/react-components”: “^6.2.0”,
“@iot-app-kit/scene-composer”: “^3.3.0”,
“@iot-app-kit/source-iottwinmaker”: “^6.2.0”,
“@react-three/drei”: “^9.74.8”,
“@react-three/fiber”: “^8.13.1”,
“@testing-library/jest-dom”: “^5.16.5”,
“@testing-library/react”: “^13.4.0”,
“@testing-library/user-event”: “^13.5.0”,
“@types/jest”: “^27.5.2”,
“@types/node”: “^16.18.35”,
“@types/react”: “^18.2.11”,
“@types/react-dom”: “^18.2.4”,
“jsx-runtime”: “^1.2.0”,
“node-polyfill-webpack-plugin”: “^2.0.1”,
“path-browserify”: “^1.0.1”,
“react”: “^18.2.0”,
“react-dom”: “^18.2.0”,
“react-scripts”: “5.0.1”,
“sass”: “^1.63.3”,
“three”: “^0.153.0”,
“three-stdlib”: “^2.23.9”,
“typescript”: “^4.9.5”,
“web-vitals”: “^2.1.4”
},
“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”,
“eject”: “react-app-rewired eject”
},
“eslintConfig”: {
“extends”: [
“react-app”,
“react-app/jest”
]
},
“browserslist”: {
“production”: [
“last 1 chrome version”
],
“development”: [
“last 1 chrome version”
]
},
“devDependencies”: {
“react-app-rewired”: “^2.2.1”
}
}

建设任务1:创建成为模板的TwinMaker场景。

进行工作

首先,使用模板的名称创建一个 TwinMaker 场景
具体操作如下两点:

    • 部屋の3Dモデルを中央に配置する

 

    カメラをCamera1の名前で作成する
scene-template.png

我从Sketchfab借用了这个模拟房间的GLB模型(其授权为CC4.0)。
链接:https://sketchfab.com/3d-models/studio-apartment-vray-baked-textures-included-cae2d96ede1d4112b1fd391099a43f77

完成这个任务后,就会变成这样

使用模板的名称创建场景时,将在与S3中对应的存储桶(存储桶结尾为-iad)中创建template.json的JSON文件。

template-s3.png

TwinMaker的场景将通过与场景名称相同的JSON文件在S3上进行统一管理。

构建作业2:创建与用户数量相同的空场景。

进行

由于本次计划是让两个用户同时登录,所以我创建了两个空场景。

    • shotaoki_mail_com

 

    other_user_mail_com

由于命名规则的限制,无法使用@等符号,因此我们使用下划线替代电子邮件地址的符号部分。

3-scenes.png

根据配额规定,您可以在一个工作区创建100个场景,所以即使再增加97个人也没有问题。

构建任务3:使用Python编写脚本,实现复制模板的处理。

structure.png

进行

    1. 用Python脚本从S3中获取一个模板的JSON文件,

 

    1. 更改对象的配置信息,覆盖现有的针对用户A的JSON文件,

 

    更改对象的配置信息,覆盖现有的针对用户B的JSON文件。

这样做可以创建适合用户A和用户B的场景。

我也从Sketchfab借用了Roomba的数据,该模型也是CC 4.0许可证。链接在这里:

源代码

用以下内容的Python文件,在Python应用程序下使用–bucketname ${S3的存储桶名称}命令来执行。

pip install pydantic boto3
from argparse import ArgumentParser
from pydantic import BaseModel
import boto3
import io
import json
from typing import List


class Argments(BaseModel):
    """
    app.pyの引数を定義する

    実行方法:
    python app.py --bucketname ${バケット名}
    """

    bucketname: str

    @classmethod
    def parse_args(cls):
        # pythonファイルの実行引数をpydanticにパースする
        parser = ArgumentParser()
        for k in cls.schema()["properties"].keys():
            parser.add_argument(f"-{k[0:1]}", f"--{k}")
        return cls.parse_obj(parser.parse_args().__dict__)


def add_roomba(
    template: dict, args: Argments, x: float, y: float, z: float, message: str
):
    """
    ルンバを部屋に召喚する

    message: アノテーションに表示するメッセージ
    x, y, z: 表示する座標
    """
    current: List[dict] = template["nodes"]

    # ルンバを配置する(glbがややズレているので、固定値でオフセットを入れる)
    current.append(
        {
            "name": "low-poly_roomba",
            "transform": {
                "position": [-3.056 + x, -0.4 + y, -2.78 + z],
                "rotation": [0, 0, 0],
                "scale": [0.5, 0.5, 0.5],
            },
            "transformConstraint": {},
            "components": [
                {
                    "type": "ModelRef",
                    "uri": f"s3://{args.bucketname}/low-poly_roomba.glb",
                    "modelType": "GLB",
                    "unitOfMeasure": "meters",
                }
            ],
            "properties": {},
        }
    )
    # ルンバの上のアノテーションを配置する
    current.append(
        {
            "name": "Annotation",
            "transform": {
                "position": [-1.2 + x, 0.1671 + y, 1.4 + z],
                "rotation": [0, 0, 0],
                "scale": [0.5, 0.5, 0.5],
            },
            "transformConstraint": {},
            "components": [
                {
                    "type": "DataOverlay",
                    "subType": "TextAnnotation",
                    "valueDataBindings": [],
                    "dataRows": [{"rowType": "Markdown", "content": message}],
                }
            ],
            "properties": {},
        }
    )
    # オブジェクトの配置情報を設定する
    template["nodes"] = current
    # 全てのオブジェクトをルートノードに設定する
    template["rootNodeIndexes"] = [r for r in range(len(current))]


def main(args: Argments):
    """
    ユーザーAのシーンを作成する
    """
    s3 = boto3.resource("s3")
    bucket = s3.Bucket(args.bucketname)

    # テンプレートシーンをコピーする
    template = {}
    with io.BytesIO() as f:
        bucket.download_fileobj("template.json", f)
        template = json.loads(f.getvalue())

    # ルンバ召喚
    add_roomba(
        template,
        args,
        x=0,
        y=0,
        z=0,
        message="""
## Aさんのルンバ
        """,
    )

    # ルンバを召還したシーンを、ユーザーAのシーンとして保存する
    with io.BytesIO(json.dumps(template).encode("utf-8")) as f:
        bucket.upload_fileobj(f, "shotaoki_mail_com.json")


def main2(args: Argments):
    """
    ユーザーBのシーンを作成する
    """
    s3 = boto3.resource("s3")
    bucket = s3.Bucket(args.bucketname)

    # テンプレートシーンをコピーする
    template = {}
    with io.BytesIO() as f:
        bucket.download_fileobj("template.json", f)
        template = json.loads(f.getvalue())

    # ルンバ召喚(※X方向に1.6メートル、Z方向に2.2メートル移動した場所)
    add_roomba(
        template,
        args,
        x=1.6,
        y=0,
        z=-2.2,
        message="""
## Bさんのルンバ
        """,
    )

    # ルンバを召還したシーンを、ユーザーBのシーンとして保存する
    with io.BytesIO(json.dumps(template).encode("utf-8")) as f:
        bucket.upload_fileobj(f, "other_user_mail_com.json")


# 処理を実行する
main(Argments.parse_args())
main2(Argments.parse_args())

環境構築4:從iot-app-kit打開場景。

进行

我打开了之前准备好的React项目,并更新了src/App.tsx文件。

import "./App.css";
import { initialize } from "@iot-app-kit/source-iottwinmaker";
import { SceneViewer } from "@iot-app-kit/scene-composer";

function App() {
  const sceneLoader = initialize("${workspace-name}", {
    awsCredentials: {
      accessKeyId: "XXXXXXXXXXXXXXX",
      secretAccessKey: "XXXXXXXXXXXXXXX",
    },
    awsRegion: "${region-name}",
  }).s3SceneLoader("${scene-name}");

  return (
    <div className="App">
      <SceneViewer sceneLoader={sceneLoader} activeCamera="Camera1" />
    </div>
  );
}

export default App;

请将变量替换为您自己的环境如下:

キー名値${workspace-name}TwinMakerのワークスペースの名前です${scene-name}シーン名です ※ログインユーザーのユーザー名を当てはめますAccessKeyIdAWSの認証情報です。※実際の運用ではCognitoを使うようにしてくださいSecretAccessKeyAWSの認証情報です。※実際の運用ではCognitoを使うようにしてくださいactiveCamera初期状態で指定するカメラです。シーン内にカメラを置いていないのなら未指定にします

移动了一下

在终端中运行 npm run start,然后在浏览器中打开 http://localhost:3000,即可显示 TwinMaker 的界面。

a-san.png

在环境构建5中,将图像放入注释。

在标注中可以显示图像。

在源代码中的部分写有“A先生的Roomba”(注释、覆盖),可以使用Markdown格式,在![](http://url.com/file.png)的格式下,可以显示喜欢的图片。

要在文本下方显示图像,需要留出两行空白。必须有文字内容。由于注释显示区域的大小取决于文字内容,所以无法仅显示图像。

## Aさんのルンバ

![](http://url.com/file.png)

请注意:在管理控制台上,注释中的图片链接不会工作。
※由于AWS管理控制台中的TwinMaker受到连接域的限制,因此图片链接无法正常使用。需要使用iot-app-kit。

总结:将登录用户的位置信息以3D方式显示。

需要获取位置信息并将其与SiteWise进行协作,然后通过Lambda定期执行来进行场景复制,就可以让登录用户将其拥有的物联网设备的位置信息显示在3D上。

使用TwinMaker,我们可以通过数字双胞胎将类似于Roomba被虚拟墙困住无法移动的情况可视化出来。您不需要进行复杂的编码,也不需要学习Blender等3D软件。这是一个很大的优点。

另外,如果将注释图像的获取源设置为ApiGateway或S3对象Lambda,则还可以在打开TwinMaker页面的瞬间显示图表。

填補

场景的更新只在重新加载时进行,TwinMaker上不能播放3D模型的动画(※通常的方法)。因此,保持仪表盘打开并实时跟踪是很困难的。

在Lambda中动态创建并传送在TwinMaker中显示的3D模型是可能的。但这种情况下,需要将Lambda定期执行产生的文件放置在S3中。通过Object Lambda的端点直接从Lambda传送3D模型供TwinMaker引用是不可行的。(*您可以使用别名通过S3端点进行传送,但由于Object Lambda的端点不能严格作为S3端点处理,所以会受到域限制的约束。)

请参考此处关于如何从图像动态创建GLTF的方法

 

广告
将在 10 秒后关闭
bannerAds