Skip to content

Commit 199a1c9

Browse files
committed
chore: bump version
1 parent a31bf5a commit 199a1c9

118 files changed

Lines changed: 21042 additions & 71 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/core/package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@thatopen/components",
33
"description": "Collection of core functionalities to author BIM apps.",
4-
"version": "3.3.3",
4+
"version": "3.4.0",
55
"author": "That Open Company",
66
"contributors": [
77
"Antonio Gonzalez Viegas (https://github.com/agviegas)",
@@ -37,24 +37,24 @@
3737
"access": "public"
3838
},
3939
"devDependencies": {
40-
"@thatopen/fragments": "~3.3.0",
40+
"@thatopen/fragments": "~3.4.0",
4141
"@thatopen/ui": "~3.3.0",
4242
"@types/convert-units": "2.3.11",
43-
"@types/three": "0.175.0",
43+
"@types/three": "0.183.1",
4444
"camera-controls": "^3.1.2",
4545
"stats.js": "^0.17.0",
46-
"three": "^0.175.0",
47-
"web-ifc": "0.0.74"
46+
"three": "0.182.0",
47+
"web-ifc": "0.0.77"
4848
},
4949
"dependencies": {
5050
"fast-xml-parser": "5.3.7",
5151
"jszip": "3.10.1",
52-
"three-mesh-bvh": "0.7.0"
52+
"three-mesh-bvh": "0.9.9"
5353
},
5454
"peerDependencies": {
55-
"@thatopen/fragments": "~3.3.0",
55+
"@thatopen/fragments": "~3.4.0",
5656
"camera-controls": ">=3.1.2",
57-
"three": ">=0.175.0",
58-
"web-ifc": ">=0.0.74"
57+
"three": ">=0.182.0",
58+
"web-ifc": ">=0.0.77"
5959
}
6060
}

packages/core/src/core/Components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,6 @@ export class Components implements Disposable {
179179
// @ts-ignore
180180
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
181181
THREE.Mesh.prototype.raycast = acceleratedRaycast;
182+
THREE.LineSegments.prototype.raycast = acceleratedRaycast;
182183
}
183184
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport"
7+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
8+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
9+
10+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
11+
<link rel="icon" type="image/x-icon" href="https://thatopen.github.io/engine_components/resources/favicon.ico">
12+
<title>FastModelPicker</title>
13+
<style>
14+
body {
15+
margin: 0;
16+
padding: 0;
17+
font-family: "Plus Jakarta Sans", sans-serif;
18+
overflow: hidden;
19+
}
20+
21+
.full-screen {
22+
width: 100vw;
23+
height: 100vh;
24+
position: relative;
25+
overflow: hidden;
26+
}
27+
28+
.options-menu {
29+
position: fixed;
30+
min-width: unset;
31+
top: 5px;
32+
right: 5px;
33+
max-height: calc(100vh - 10px);
34+
}
35+
36+
.phone-menu-toggler {
37+
visibility: hidden;
38+
}
39+
40+
@media (max-width: 480px) {
41+
.options-menu {
42+
visibility: hidden;
43+
bottom: 5px;
44+
left: 5px;
45+
}
46+
47+
.options-menu-visible {
48+
visibility: visible;
49+
}
50+
51+
.phone-menu-toggler {
52+
visibility: visible;
53+
position: fixed;
54+
top: 5px;
55+
right: 5px;
56+
}
57+
}
58+
59+
</style>
60+
</head>
61+
62+
<body>
63+
<div class="full-screen" id="container"></div>
64+
<script type="module" src="./test.ts"></script>
65+
</body>
66+
67+
</html>
68+
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/* MD
2+
## 📄 Fast Model Picking with Color Coding
3+
---
4+
This tutorial demonstrates how to use the FastModelPicker component to quickly identify which fragment model is under the mouse cursor. Unlike raycasting, this component uses color coding for extremely fast model identification, making it ideal for scenarios where you need to quickly determine which model the user is hovering over or clicking on.
5+
6+
### 🖖 Importing our Libraries
7+
First things first, let's install all necessary dependencies to make this example work:
8+
*/
9+
10+
import * as THREE from "three";
11+
import Stats from "stats.js";
12+
// You have to import * as OBC from "@thatopen/components"
13+
import * as OBC from "../..";
14+
15+
/* MD
16+
### 🌎 Setting up a Simple Scene
17+
To get started, let's set up a basic ThreeJS scene. This will serve as the foundation for our application and allow us to visualize the 3D models effectively:
18+
*/
19+
20+
const components = new OBC.Components();
21+
22+
const worlds = components.get(OBC.Worlds);
23+
const world = worlds.create<
24+
OBC.SimpleScene,
25+
OBC.OrthoPerspectiveCamera,
26+
OBC.SimpleRenderer
27+
>();
28+
29+
world.scene = new OBC.SimpleScene(components);
30+
world.scene.setup();
31+
world.scene.three.background = null;
32+
33+
const container = document.getElementById("container")!;
34+
world.renderer = new OBC.SimpleRenderer(components, container);
35+
world.camera = new OBC.OrthoPerspectiveCamera(components);
36+
await world.camera.controls.setLookAt(68, 23, -8.5, 21.5, -5.5, 23);
37+
38+
components.init();
39+
40+
/* MD
41+
### 🛠️ Setting Up Fragments
42+
Now, let's configure the FragmentsManager. This will allow us to load models effortlessly and start manipulating them with ease:
43+
*/
44+
45+
const workerUrl =
46+
"/node_modules/@thatopen/fragments/dist/Worker/worker.mjs";
47+
const fragments = components.get(OBC.FragmentsManager);
48+
fragments.init(workerUrl);
49+
50+
world.camera.controls.addEventListener("rest", () =>
51+
fragments.core.update(true),
52+
);
53+
54+
world.onCameraChanged.add((camera) => {
55+
for (const [, model] of fragments.list) {
56+
model.useCamera(camera.three);
57+
}
58+
fragments.core.update(true);
59+
});
60+
61+
fragments.list.onItemSet.add(({ value: model }) => {
62+
model.useCamera(world.camera.three);
63+
world.scene.three.add(model.object);
64+
fragments.core.update(true);
65+
});
66+
67+
/* MD
68+
### 📂 Loading Fragments Models
69+
With the core setup complete, it's time to load Fragments models into our scene. We'll load multiple models to demonstrate the picker's ability to distinguish between them:
70+
*/
71+
72+
const fragPaths = [
73+
"/resources/frags/school_arq.frag",
74+
"/resources/frags/school_str.frag",
75+
];
76+
77+
await Promise.all(
78+
fragPaths.map(async (path) => {
79+
const modelId = path.split("/").pop()?.split(".").shift();
80+
if (!modelId) return null;
81+
const file = await fetch(path);
82+
const buffer = await file.arrayBuffer();
83+
return fragments.core.load(buffer, { modelId });
84+
}),
85+
);
86+
87+
/* MD
88+
### ✨ Using The FastModelPicker Component
89+
Now let's set up the FastModelPicker. This component uses color coding to quickly identify which model is under the mouse cursor:
90+
*/
91+
92+
const pickers = components.get(OBC.FastModelPickers);
93+
const picker = pickers.get(world);
94+
95+
/* MD
96+
### 🎨 Enabling Debug Mode
97+
Debug mode shows a small canvas in the top-right corner displaying the color-coded render. This is useful for understanding how the component works:
98+
*/
99+
100+
picker.setDebugMode(true);
101+
102+
/* MD
103+
### 🖱️ Picking Models on Mouse Move
104+
Let's add an event listener to detect which model is under the mouse cursor as you move it:
105+
*/
106+
107+
const infoPanel = document.createElement("div");
108+
infoPanel.style.position = "fixed";
109+
infoPanel.style.top = "10px";
110+
infoPanel.style.left = "10px";
111+
infoPanel.style.backgroundColor = "rgba(0, 0, 0, 0.8)";
112+
infoPanel.style.color = "white";
113+
infoPanel.style.padding = "10px";
114+
infoPanel.style.borderRadius = "5px";
115+
infoPanel.style.fontFamily = "monospace";
116+
infoPanel.style.zIndex = "10000";
117+
infoPanel.innerHTML = "Move your mouse over the models...";
118+
document.body.appendChild(infoPanel);
119+
120+
container.addEventListener("pointermove", async (event) => {
121+
// Get mouse position in normalized coordinates
122+
const bounds = container.getBoundingClientRect();
123+
const x = ((event.clientX - bounds.left) / bounds.width) * 2 - 1;
124+
const y = -((event.clientY - bounds.top) / bounds.height) * 2 + 1;
125+
const position = new THREE.Vector2(x, y);
126+
127+
// Get model ID at mouse position
128+
const modelId = await picker.getModelAt(position);
129+
130+
if (modelId) {
131+
infoPanel.innerHTML = `Model ID: <strong>${modelId}</strong>`;
132+
infoPanel.style.color = "lime";
133+
} else {
134+
infoPanel.innerHTML = "No model detected";
135+
infoPanel.style.color = "white";
136+
}
137+
});
138+
139+
/* MD
140+
### 🎯 Picking Models on Click
141+
You can also pick models on click events:
142+
*/
143+
144+
container.addEventListener("click", async (event) => {
145+
const bounds = container.getBoundingClientRect();
146+
const x = ((event.clientX - bounds.left) / bounds.width) * 2 - 1;
147+
const y = -((event.clientY - bounds.top) / bounds.height) * 2 + 1;
148+
const position = new THREE.Vector2(x, y);
149+
150+
const modelId = await picker.getModelAt(position);
151+
if (modelId) {
152+
console.log("Clicked on model:", modelId);
153+
}
154+
});
155+
156+
/* MD
157+
### 📊 Performance Stats
158+
Let's add performance monitoring to see how fast the picker is:
159+
*/
160+
161+
const stats = new Stats();
162+
stats.showPanel(2);
163+
document.body.append(stats.dom);
164+
stats.dom.style.left = "0px";
165+
stats.dom.style.zIndex = "unset";
166+
167+
world.renderer.onBeforeUpdate.add(() => stats.begin());
168+
world.renderer.onAfterUpdate.add(() => stats.end());
169+
170+
/* MD
171+
### 💡 Tips
172+
- The FastModelPicker is much faster than raycasting for simple model identification
173+
- Debug mode is useful for understanding how the color coding works
174+
- The component automatically handles multiple models and assigns unique colors to each
175+
- You can disable debug mode by calling `picker.setDebugMode(false)`
176+
*/
177+

packages/core/src/core/Types/src/interfaces.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,28 @@ export interface Serializable<
164164
import: (result: SerializationResult<D, S>, ...args: any) => any;
165165
export: (...args: any) => SerializationResult<D, S>;
166166
}
167+
168+
/**
169+
* Whether this component manages its interaction through an explicit state machine.
170+
* The machine is the single source of truth: the system can only be in one state at
171+
* a time, and every transition is deterministic given the current state and the event.
172+
*
173+
* @template TState - Discriminated union of all valid states (each with a `kind` string).
174+
* @template TEvent - Discriminated union of all accepted events (each with a `type` string).
175+
*/
176+
export interface Transitionable<
177+
TState extends { kind: string },
178+
TEvent extends { type: string },
179+
> {
180+
/** The current state. TypeScript guarantees it is always a valid, well-typed state. */
181+
readonly machineState: TState;
182+
183+
/**
184+
* Dispatches an event to the machine, producing a deterministic state transition.
185+
* Events that do not apply to the current state are silently ignored.
186+
*/
187+
sendMachineEvent(event: TEvent): void;
188+
189+
/** Fired synchronously after every state transition with the new state as payload. */
190+
readonly onMachineStateChanged: Event<TState>;
191+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Component, Components } from "../../core";
2+
import { DxfExporter } from "./src/DxfExporter";
3+
4+
export * from "./src";
5+
6+
/**
7+
* Manages DXF import and export for technical drawings.
8+
*
9+
* ```ts
10+
* const manager = components.get(OBC.DxfManager);
11+
* const dxf = manager.exporter.export([{ drawing, viewports: [{ viewport }] }]);
12+
* ```
13+
*/
14+
export class DxfManager extends Component {
15+
static readonly uuid = "e9a2c3d4-5f67-4b89-a012-1c3d5e7f9b2a" as const;
16+
17+
enabled = true;
18+
19+
/** Handles DXF serialisation of {@link TechnicalDrawing} content. */
20+
readonly exporter = new DxfExporter(this.components);
21+
22+
constructor(components: Components) {
23+
super(components);
24+
components.add(DxfManager.uuid, this);
25+
}
26+
}

0 commit comments

Comments
 (0)