Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,12 @@ node-app
.claude/

resources/frags/test/

# local IFC projects: models and the generated gallery are not committed
/projects/*/
/projects/index.html
/projects/.dev-server.*

# pnpm artifacts (local setup; the repo installs with yarn)
pnpm-lock.yaml
pnpm-workspace.yaml
78 changes: 40 additions & 38 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,46 @@

<body>
<div style="display: flex; flex-direction: column; gap: 1rem;">
<h3>Choose an example</h3>
<a style="width: fit-content;" href="./packages\front\src\measurement\VolumeMeasurement\example.html">front/VolumeMeasurement</a>
<a style="width: fit-content;" href="./packages\front\src\measurement\LengthMeasurement\example.html">front/LengthMeasurement</a>
<a style="width: fit-content;" href="./packages\front\src\measurement\AreaMeasurement\example.html">front/AreaMeasurement</a>
<a style="width: fit-content;" href="./packages\front\src\measurement\AngleMeasurement\example.html">front/AngleMeasurement</a>
<a style="width: fit-content;" href="./packages\front\src\fragments\Outliner\example.html">front/Outliner</a>
<a style="width: fit-content;" href="./packages\front\src\fragments\Hoverer\example.html">front/Hoverer</a>
<a style="width: fit-content;" href="./packages\front\src\fragments\Highlighter\example.html">front/Highlighter</a>
<a style="width: fit-content;" href="./packages\front\src\drawings\DrawingEditor\example.html">front/DrawingEditor</a>
<a style="width: fit-content;" href="./packages\front\src\core\PostproductionRenderer\example.html">front/PostproductionRenderer</a>
<a style="width: fit-content;" href="./packages\front\src\core\Marker\example.html">front/Marker</a>
<a style="width: fit-content;" href="./packages\front\src\core\ClipStyler\example.html">front/ClipStyler</a>
<a style="width: fit-content;" href="./packages\front\src\civil\CivilNavigators\example.html">front/CivilNavigators</a>
<a style="width: fit-content;" href="./packages\core\src\openbim\IDSSpecifications\example.html">core/IDSSpecifications</a>
<a style="width: fit-content;" href="./packages\core\src\openbim\BCFTopics\example.html">core/BCFTopics</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\ItemsFinder\example.html">core/ItemsFinder</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\IfcLoader\example.html">core/IfcLoader</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\Hider\example.html">core/Hider</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\FragmentsManager\example.html">core/FragmentsManager</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\EdgeProjector\example.html">core/EdgeProjector</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\Classifier\example.html">core/Classifier</a>
<a style="width: fit-content;" href="./packages\core\src\fragments\BoundingBoxer\example.html">core/BoundingBoxer</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\example.html">core/TechnicalDrawings</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\MultiDrawingViewports\example.html">core/MultiDrawingViewports</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\ModelDrivenAnnotations\example.html">core/ModelDrivenAnnotations</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\DrawingLayers\example.html">core/DrawingLayers</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\DrawingBlocks\example.html">core/DrawingBlocks</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\CustomAnnotationSystems\example.html">core/CustomAnnotationSystems</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\AnnotationSystems\example.html">core/AnnotationSystems</a>
<a style="width: fit-content;" href="./packages\core\src\drawings\TechnicalDrawings\examples\AnnotationStyles\example.html">core/AnnotationStyles</a>
<a style="width: fit-content;" href="./packages\core\src\core\Worlds\example.html">core/Worlds</a>
<a style="width: fit-content;" href="./packages\core\src\core\Views\example.html">core/Views</a>
<a style="width: fit-content;" href="./packages\core\src\core\Viewpoints\example.html">core/Viewpoints</a>
<a style="width: fit-content;" href="./packages\core\src\core\ShadowedScene\example.html">core/ShadowedScene</a>
<a style="width: fit-content;" href="./packages\core\src\core\Raycasters\example.html">core/Raycasters</a>
<a style="width: fit-content;" href="./packages\core\src\core\OrthoPerspectiveCamera\example.html">core/OrthoPerspectiveCamera</a>
<a style="width: fit-content;" href="./packages\core\src\core\Grids\example.html">core/Grids</a>
<a style="width: fit-content;" href="./packages\core\src\core\Clipper\example.html">core/Clipper</a>
<h3>Projects</h3>
<a style="width: fit-content;" href="./projects/viewer.html">New empty session (multimodel)</a>
<h3>Choose an example</h3>
<a style="width: fit-content;" href="./packages/front/src/measurement/VolumeMeasurement/example.html">front/VolumeMeasurement</a>
<a style="width: fit-content;" href="./packages/front/src/measurement/LengthMeasurement/example.html">front/LengthMeasurement</a>
<a style="width: fit-content;" href="./packages/front/src/measurement/AreaMeasurement/example.html">front/AreaMeasurement</a>
<a style="width: fit-content;" href="./packages/front/src/measurement/AngleMeasurement/example.html">front/AngleMeasurement</a>
<a style="width: fit-content;" href="./packages/front/src/fragments/Outliner/example.html">front/Outliner</a>
<a style="width: fit-content;" href="./packages/front/src/fragments/Hoverer/example.html">front/Hoverer</a>
<a style="width: fit-content;" href="./packages/front/src/fragments/Highlighter/example.html">front/Highlighter</a>
<a style="width: fit-content;" href="./packages/front/src/drawings/DrawingEditor/example.html">front/DrawingEditor</a>
<a style="width: fit-content;" href="./packages/front/src/core/PostproductionRenderer/example.html">front/PostproductionRenderer</a>
<a style="width: fit-content;" href="./packages/front/src/core/Marker/example.html">front/Marker</a>
<a style="width: fit-content;" href="./packages/front/src/core/ClipStyler/example.html">front/ClipStyler</a>
<a style="width: fit-content;" href="./packages/front/src/civil/CivilNavigators/example.html">front/CivilNavigators</a>
<a style="width: fit-content;" href="./packages/core/src/openbim/IDSSpecifications/example.html">core/IDSSpecifications</a>
<a style="width: fit-content;" href="./packages/core/src/openbim/BCFTopics/example.html">core/BCFTopics</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/ItemsFinder/example.html">core/ItemsFinder</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/IfcLoader/example.html">core/IfcLoader</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/Hider/example.html">core/Hider</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/FragmentsManager/example.html">core/FragmentsManager</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/EdgeProjector/example.html">core/EdgeProjector</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/Classifier/example.html">core/Classifier</a>
<a style="width: fit-content;" href="./packages/core/src/fragments/BoundingBoxer/example.html">core/BoundingBoxer</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/example.html">core/TechnicalDrawings</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/MultiDrawingViewports/example.html">core/MultiDrawingViewports</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/ModelDrivenAnnotations/example.html">core/ModelDrivenAnnotations</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/DrawingLayers/example.html">core/DrawingLayers</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/DrawingBlocks/example.html">core/DrawingBlocks</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/CustomAnnotationSystems/example.html">core/CustomAnnotationSystems</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/AnnotationSystems/example.html">core/AnnotationSystems</a>
<a style="width: fit-content;" href="./packages/core/src/drawings/TechnicalDrawings/examples/AnnotationStyles/example.html">core/AnnotationStyles</a>
<a style="width: fit-content;" href="./packages/core/src/core/Worlds/example.html">core/Worlds</a>
<a style="width: fit-content;" href="./packages/core/src/core/Views/example.html">core/Views</a>
<a style="width: fit-content;" href="./packages/core/src/core/Viewpoints/example.html">core/Viewpoints</a>
<a style="width: fit-content;" href="./packages/core/src/core/ShadowedScene/example.html">core/ShadowedScene</a>
<a style="width: fit-content;" href="./packages/core/src/core/Raycasters/example.html">core/Raycasters</a>
<a style="width: fit-content;" href="./packages/core/src/core/OrthoPerspectiveCamera/example.html">core/OrthoPerspectiveCamera</a>
<a style="width: fit-content;" href="./packages/core/src/core/Grids/example.html">core/Grids</a>
<a style="width: fit-content;" href="./packages/core/src/core/Clipper/example.html">core/Clipper</a>

</div>
</body>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"eslint-plugin-prettier": "5.1.3",
"glob": "latest",
"prettier": "3.2.5",
"three": "0.184.0",
"typescript": "5.4.2",
"vite": "7.3.6",
"vite-plugin-dts": "4.5.4"
Expand Down
8 changes: 7 additions & 1 deletion packages/front/src/core/Marker/src/renderer-with-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ export class RendererWith2D extends SimpleRenderer {
this.three2D.domElement.style.pointerEvents = "none";
if (this.container) {
this.container.appendChild(this.three2D.domElement);
this.container.style.position = "relative";
// Only set the positioning context when the app has not established
// one already — clobbering e.g. an absolutely positioned full-screen
// container would break the app layout.
const computed = window.getComputedStyle(this.container);
if (computed.position === "static") {
this.container.style.position = "relative";
}
}
}
}
16 changes: 9 additions & 7 deletions packages/front/src/measurement/AngleMeasurement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import { Mark } from "../../core";
import { Measurement } from "../Measurement";
import { MeasureLine } from "./src/types";
import { newDimensionMark, newEndPoint } from "../utils";
import {
AngleMeasurerModes,
AngleMeasurerTempData,
AngleVisual,
} from "./src";
import { AngleMeasurerModes, AngleMeasurerTempData, AngleVisual } from "./src";

const ARC_SEGMENTS = 32;
const ARC_RADIUS_FACTOR = 0.3;
Expand Down Expand Up @@ -155,14 +151,18 @@ export class AngleMeasurement extends Measurement<Angle, "angle"> {
} else if (this._temp.clickCount === 2) {
// Third click: set end and finalize
this._temp.angle.end.copy(point);
this._temp.clickCount = 3;
this.endCreation();
}
};

/** {@link OBC.Createable.endCreation}. Finalizes the current angle after the third point is set. */
endCreation = () => {
if (!this.enabled) return;
if (this._temp.clickCount !== 2) return;
// Only commit once the third point has been set: an external
// Enter press after just two points would otherwise finalize an
// angle whose end is still the origin.
if (this._temp.clickCount !== 3) return;

this.disposeAllPreview();
this.list.add(this._temp.angle.clone());
Expand Down Expand Up @@ -526,6 +526,8 @@ export class AngleMeasurement extends Measurement<Angle, "angle"> {
normal.normalize();

const midDir = normalizedA.clone().applyAxisAngle(normal, angle / 2);
return vertex.clone().add(midDir.multiplyScalar(radius * LABEL_OFFSET_FACTOR));
return vertex
.clone()
.add(midDir.multiplyScalar(radius * LABEL_OFFSET_FACTOR));
}
}
21 changes: 16 additions & 5 deletions packages/front/src/measurement/AreaMeasurement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,23 @@ export class AreaMeasurement extends Measurement<Area, "area"> {
this._temp.isDragging = true;
}

if (area.points.size === 0) {
point.copy(pickerData.point);
area.points.add(point.clone());
} else {
area.points.add(point.clone());
// Always consume the click-time pick (`pickerData`). The preview
// point (`_temp.point`) is driven by the pointermove pick, which
// can be stale right after camera interaction — committing it
// would place the vertex where the cursor *was*, not under the
// double-click. Project onto the in-progress plane when the point
// falls within the tolerance so the polygon stays planar.
point.copy(pickerData.point);
const { plane } = area;
if (plane) {
const distance = plane.distanceToPoint(point);
if (Math.abs(distance) < this.pickTolerance) {
const projection = new THREE.Vector3();
plane.projectPoint(point, projection);
point.copy(projection);
}
}
area.points.add(point.clone());

if (this.mode === "square" && area.points.size === 2 && pickerData.normal) {
const [p1, p3] = area.points;
Expand Down
27 changes: 20 additions & 7 deletions packages/front/src/measurement/LengthMeasurement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class LengthMeasurement extends Measurement<Line, "length"> {
private _edgeMissStreak = 0;
private static readonly EDGE_MISS_TOLERANCE = 3;


/**
* The possible modes in which a measurement of this type may be created.
*/
Expand Down Expand Up @@ -90,12 +89,15 @@ export class LengthMeasurement extends Measurement<Line, "length"> {
throw new Error("Measurement: world is need!");
}

// Prefer the latest per-frame pick (matches what the snap marker
// is showing), fall back to a fresh pick when the user clicks
// before any pointer-move has fired in this session.
const pickResult =
this.lastPick ??
(await this._vertexPicker.get({ snappingClasses: this.snappings }));
// Always pick fresh at click time. `lastPick` is the last RAF pick
// driven by pointermove, which goes stale whenever the camera
// moves without the cursor moving (wheel zoom suspends picks for
// `cameraStopDelay` ms); reusing it would anchor the measurement
// to where the cursor *was*, not to the surface under the
// double-click.
const pickResult = await this._vertexPicker.get({
snappingClasses: this.snappings,
});

if (this.mode === "free") {
if (!pickResult?.point) return;
Expand Down Expand Up @@ -196,6 +198,17 @@ export class LengthMeasurement extends Measurement<Line, "length"> {
await this.initPreview();
return;
}
// Refresh from a click-time pick before committing: the preview
// follows `lastPick` (pointermove-driven), which can lag or be
// stale right after camera interaction, so committing the preview
// as-is would anchor the endpoint away from where the user
// actually double-clicked. `updatePreviewLine` consumes the fresh
// `lastPick` in both modes (free copies the point into the line,
// edge re-snaps the edge) and the edge commit guard reads it too.
this.lastPick = await this._vertexPicker.get({
snappingClasses: this.snappings,
});
this.updatePreviewLine();
this.endCreation();
};

Expand Down
41 changes: 41 additions & 0 deletions projects/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Projects

Drop IFC models into this folder to load them from the dev server.

## Layout

```
projects/
├── viewer.html / viewer.ts # multi-model IFC viewer
├── <name>/
│ ├── model.ifc # one directory per project
│ └── project.title # optional display title (first line)
└── index.html # optional gallery, generated by your tooling
```

Every directory that contains an `.ifc` file is listed on the dev server home
page and opens in `viewer.html`.

## Viewer

- **Add IFC…** — the only load button: pick one or more IFC files and append
them to the current session. Works on an empty scene too (empty session =
open `viewer.html` with no query params, then add files).
- **Models panel** — list of loaded models with per-model hide/unload and a
coordinate mode:
- `shared (first model)` (default) — every model is moved into the
coordinate system of the first loaded one
(`position = coords_first − coords_i`, read from `model.getCoordinates()`);
- `from each file` — keeps the per-file normalization web-ifc applied while
loading (`COORDINATE_TO_ORIGIN`).
- Drag & drop also appends files.
- The page validates the IFC signature (`ISO-10303-21`) before conversion, so
a wrong URL or a broken file shows an error instead of crashing the tab.

Models can be loaded straight from the URL, one `?model=` parameter per file:

```
projects/viewer.html?model=name-a/model.ifc&model=name-b/model.ifc&title=Project
```

For debugging, `window.__bim` exposes `{ components, world, fragments, ifcLoader }`.
Loading