├── .DS_Store
├── .eslintrc.cjs
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── publish-npm.yml
│ └── release-please.yml
├── .gitignore
├── .yarn
└── releases
│ └── yarn-3.2.1.cjs
├── .yarnrc.yml
├── LICENSE.md
├── README.md
├── index.html
├── jest.config.ts
├── package.json
├── packages
└── clay
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── core
│ │ ├── Elements
│ │ │ ├── DynamicElementType
│ │ │ │ └── index.ts
│ │ │ ├── Element
│ │ │ │ └── index.ts
│ │ │ ├── ElementType
│ │ │ │ └── index.ts
│ │ │ ├── StaticElementType
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Event
│ │ │ └── index.ts
│ │ ├── Geometry
│ │ │ └── index.ts
│ │ ├── Model
│ │ │ └── index.ts
│ │ ├── Object
│ │ │ └── index.ts
│ │ ├── Object3D
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── elements
│ │ ├── CurtainWalls
│ │ │ ├── SimpleCurtainWall
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Furniture
│ │ │ ├── SimpleFurniture
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Members
│ │ │ ├── SimpleMember
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Openings
│ │ │ ├── SimpleOpening
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Plates
│ │ │ ├── SimplePlate
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Slabs
│ │ │ ├── SimpleSlab
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── Walls
│ │ │ ├── SimpleWall
│ │ │ │ ├── example.html
│ │ │ │ ├── example.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── simple-wall-cornerer.ts
│ │ │ │ │ ├── simple-wall-nester.ts
│ │ │ │ │ └── simple-wall.ts
│ │ │ └── index.ts
│ │ ├── Windows
│ │ │ ├── SimpleWindow
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ └── src
│ │ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── general
│ │ ├── ElementChildren
│ │ │ └── index.ts
│ │ ├── Project
│ │ │ └── index.ts
│ │ ├── Site
│ │ │ └── index.ts
│ │ ├── SpatialChildren
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── geometries
│ │ ├── Brep
│ │ │ ├── index.html
│ │ │ └── index.ts
│ │ ├── Extrusion
│ │ │ ├── index.html
│ │ │ └── index.ts
│ │ ├── HalfSpace
│ │ │ ├── index.html
│ │ │ └── index.ts
│ │ ├── MappedItems
│ │ │ ├── index.html
│ │ │ └── index.ts
│ │ ├── Profiles
│ │ │ ├── ArbitraryClosedProfile
│ │ │ │ └── index.ts
│ │ │ ├── Profile
│ │ │ │ └── index.ts
│ │ │ ├── RectangleProfile
│ │ │ │ ├── index.html
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── primitives
│ │ ├── Extrusions
│ │ │ ├── index.html
│ │ │ └── index.ts
│ │ ├── Faces
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Lines
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── OffsetFaces
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── OffsetFaces3D
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Planes
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Polygons
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Primitive
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Snapper
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── Vertices
│ │ │ ├── index.html
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ └── index.ts
│ └── utils
│ │ ├── buffer-manager.ts
│ │ ├── event.ts
│ │ ├── id-index-map.ts
│ │ ├── ifc-utils.ts
│ │ ├── index.ts
│ │ ├── math-utils.ts
│ │ ├── raycaster.ts
│ │ ├── selector.ts
│ │ ├── transform-controls.ts
│ │ └── vector.ts
│ ├── tsconfig-build.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── resources
├── .DS_Store
├── cover.png
├── door.glb
├── doors.glb
├── favicon.ico
├── simple-window.blend
└── simple-window.glb
├── reviewpad.yml
├── tsconfig.jest.json
├── vite.config-examples.ts
└── yarn.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/.DS_Store
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: ["airbnb-base", "prettier"],
7 | parser: "@typescript-eslint/parser",
8 | parserOptions: {
9 | ecmaVersion: 12,
10 | sourceType: "module",
11 | },
12 | plugins: ["@typescript-eslint", "prettier"],
13 | ignorePatterns: ["**/dist/*", "**/node_modules/*", "**/*.json", "**/*.js"],
14 | rules: {
15 | "prettier/prettier": [
16 | "error",
17 | {
18 | endOfLine: "auto",
19 | },
20 | ],
21 | indent: "off",
22 | "no-shadow": "off",
23 | "lines-between-class-members": "off",
24 | "linebreak-style": "off",
25 | "arrow-body-style": "off",
26 | "prefer-destructuring": "off",
27 | "no-console": "off",
28 | "no-param-reassign": "off",
29 | "eol-last": "off",
30 | "no-unused-vars": "off",
31 | "class-methods-use-this": "off",
32 | "no-await-in-loop": "off",
33 | "no-return-assign": "off",
34 | "no-restricted-syntax": "off",
35 | "no-useless-constructor": "off",
36 | "no-empty-function": "off",
37 | "no-continue": "off",
38 | "no-underscore-dangle": "off",
39 | "guard-for-in": "off",
40 | "import/extensions": [
41 | "error",
42 | "ignorePackages",
43 | {
44 | ts: "never",
45 | },
46 | ],
47 | "import/prefer-default-export": "off",
48 | "no-plusplus": "off",
49 | },
50 | settings: {
51 | "import/resolver": {
52 | node: {
53 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
54 | },
55 | },
56 | },
57 | overrides: [
58 | {
59 | files: ["*.test.ts"],
60 | env: {
61 | jest: true,
62 | },
63 | },
64 | ],
65 | };
66 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: "Bug report 🐛"
2 | description: Report an issue, bug or unexpected behaviour
3 | labels: [bug]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this bug report! 🙏
9 | - type: textarea
10 | id: bug-description
11 | attributes:
12 | label: Describe the bug 📝
13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
14 | placeholder: |
15 | I am doing ...
16 | What I expect is ...
17 | What is actually happening is...
18 | validations:
19 | required: true
20 | - type: input
21 | id: reproduction
22 | attributes:
23 | label: Reproduction ▶️
24 | description: If possible, please provide an [asciinema record](https://asciinema.org/) or a link to a repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is desirable. If a report is vague (e.g. just a generic error message) and has no reproduction, solving it will also be a slower or unfeasible process.
25 | placeholder: Reproduction URL
26 | validations:
27 | required: false
28 | - type: textarea
29 | id: reproduction-steps
30 | attributes:
31 | label: Steps to reproduce 🔢
32 | description: Please provide any reproduction steps that may need to be described.
33 | placeholder: |
34 | 1. Run `npm install`.
35 | 2. ...
36 | validations:
37 | required: false
38 | - type: textarea
39 | id: system-info
40 | attributes:
41 | label: System Info 💻
42 | description: Output of `npx envinfo --system --npmPackages 'openbim-components' --binaries --browsers`
43 | render: shell
44 | placeholder: System, Binaries, Browsers
45 | validations:
46 | required: true
47 | - type: dropdown
48 | id: package-manager
49 | attributes:
50 | label: Used Package Manager 📦
51 | description: Select the used package manager
52 | options:
53 | - npm
54 | - yarn
55 | - pnpm
56 | validations:
57 | required: true
58 | - type: textarea
59 | id: logs
60 | attributes:
61 | label: Error Trace/Logs 📃
62 | description: |
63 | Optional if provided reproduction. Please try not to insert an image but copy paste the error trace/logs text.
64 |
65 | Provide the error log here in the format below:
66 | ````
67 |
68 | Click to expand!
69 |
70 | ```shell
71 | // paste the log text here
72 | ```
73 |
74 | ````
75 | - type: checkboxes
76 | id: checkboxes
77 | attributes:
78 | label: Validations ✅
79 | description: "Before submitting the issue, please make sure that you have:"
80 | options:
81 | - label: Read the [docs](https://docs.thatopen.com/intro).
82 | required: true
83 | - label: Check that there isn't [already an issue](https://github.com/ThatOpen/engine_components/issues) that reports the same bug to avoid creating a duplicate.
84 | required: true
85 | - label: Make sure this is a repository issue and not a framework-specific issue. For example, if it's a THREE.js related bug, it should likely be reported to [mrdoob/threejs](https://github.com/mrdoob/three.js) instead.
86 | required: true
87 | - label: Check that this is a concrete bug. For Q&A join our [Community](https://people.thatopen.com/).
88 | required: true
89 | - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
90 | required: true
91 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Community
3 | url: https://people.thatopen.com/
4 | about: Join the community and discuss with other BIM software developers in real time.
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "New feature proposal 🚀"
2 | description: Propose a new feature or cool thing
3 | labels: [feature]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for your interest in the project and taking the time to fill out this feature report! 🙏
9 | - type: textarea
10 | id: feature-description
11 | attributes:
12 | label: Description 📝
13 | description: "Clear and concise description of the problem. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks!"
14 | placeholder: "Example: As a BIM developer/engineer using IFC.js I want [goal / wish] so that [benefit]."
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: suggested-solution
19 | attributes:
20 | label: Suggested solution 💡
21 | description: If possible, include in this field a solution or approach that would solve or implement the described feature.
22 | placeholder: "In module/component [xy] we could provide the following implementation..."
23 | validations:
24 | required: false
25 | - type: textarea
26 | id: alternative
27 | attributes:
28 | label: Alternative ⛕
29 | description: Clear and concise description of any alternative solutions or features you've considered.
30 | validations:
31 | required: false
32 | - type: textarea
33 | id: additional-context
34 | attributes:
35 | label: Additional context ☝️
36 | description: Any other context or screenshots about the feature request here.
37 | validations:
38 | required: false
39 | - type: checkboxes
40 | id: checkboxes
41 | attributes:
42 | label: Validations ✅
43 | description: "Before submitting the issue, please make sure that you have:"
44 | options:
45 | - label: Read the [docs](https://docs.thatopen.com/intro).
46 | required: true
47 | - label: Check that there isn't [already an issue](https://github.com/ThatOpen/engine_components/issues) that requests the same feature to avoid creating a duplicate.
48 | required: true
49 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Description
4 |
5 |
6 |
7 | ### Additional context
8 |
9 |
10 |
11 | ---
12 |
13 | ### What is the purpose of this pull request?
14 |
15 | - [ ] Bug fix
16 | - [ ] New Feature
17 | - [ ] Documentation update
18 | - [ ] Other
19 |
20 | ### Before submitting the PR, please make sure you do the following:
21 |
22 | - [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
23 | - [ ] Follow the [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) standard for PR naming (e.g. `feat(examples): add hello-world example`).
24 | - [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
25 | - [ ] Ideally, include relevant tests that fail without this PR but pass with it.
26 |
--------------------------------------------------------------------------------
/.github/workflows/publish-npm.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/publish-npm.yml
2 | # This workflow pushes new published releases to https://www.npmjs.com using
3 | # the Yarn Package Manager.
4 | name: Publish package to npmjs (using Yarn)
5 | on:
6 | release:
7 | types: [published]
8 | workflow_call:
9 | secrets:
10 | NPM_TOKEN:
11 | required: true
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/setup-node@v3
18 | with:
19 | node-version: '18.16.x' # Use LTS
20 | registry-url: 'https://registry.npmjs.org'
21 | scope: '@octocat'
22 | - run: yarn
23 | - run: yarn publish-repo
24 | env:
25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/release-please.yml
2 | # This workflow handles the release-please system that manages releasing new versions.
3 | # It also calls `publish-npm.yml`, which then handles publishing to npmjs.
4 | # See: https://github.com/googleapis/release-please
5 | name: release-please
6 | on:
7 | push:
8 | branches:
9 | - main
10 | permissions:
11 | contents: write
12 | pull-requests: write
13 | jobs:
14 | release-please:
15 | runs-on: ubuntu-latest
16 | outputs:
17 | release_created: ${{ steps.release.outputs.release_created }}
18 | steps:
19 | - uses: google-github-actions/release-please-action@v3 # Handle local releases
20 | id: release
21 | with:
22 | release-type: node
23 | package-name: openbim-components
24 | publish-npm:
25 | needs: release-please
26 | if: ${{ needs.release-please.outputs.release_created }}
27 | uses: ./.github/workflows/publish-npm.yml # Publish to npmjs
28 | secrets:
29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
30 | # Publish only if release-please creates a published release
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 |
93 | # Gatsby files
94 | .cache/
95 | # Comment in the public line in if your project uses Gatsby and not Next.js
96 | # https://nextjs.org/blog/next-9-1#public-directory-support
97 | # public
98 |
99 | # vuepress build output
100 | .vuepress/dist
101 |
102 | # vuepress v2.x temp and cache directory
103 | .temp
104 | .cache
105 |
106 | # Docusaurus cache and generated files
107 | .docusaurus
108 |
109 | # Serverless directories
110 | .serverless/
111 |
112 | # FuseBox cache
113 | .fusebox/
114 |
115 | # DynamoDB Local files
116 | .dynamodb/
117 |
118 | # TernJS port file
119 | .tern-port
120 |
121 | # Stores VSCode versions used for testing VSCode extensions
122 | .vscode-test
123 |
124 | # yarn v2
125 | .yarn/cache
126 | .yarn/unplugged
127 | .yarn/build-state.yml
128 | .yarn/install-state.gz
129 | .pnp.*
130 | .idea/
131 | dist/
132 | resources/asdf.ifc
133 | resources/asdf.frag
134 | resources/asdf.json
135 | resources/bbbb.*
136 |
137 |
138 |
139 | resources/asdf2.frag
140 | resources/asdf2.json
141 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | yarnPath: .yarn/releases/yarn-3.2.1.cjs
2 | nodeLinker: node-modules
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining
2 | a copy of this software and associated documentation files (the
3 | "Software"), to deal in the Software without restriction, including
4 | without limitation the rights to use, copy, modify, merge, publish,
5 | distribute, sublicense, and/or sell copies of the Software, and to
6 | permit persons to whom the Software is furnished to do so, subject to
7 | the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be
10 | included in all copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | TOC
3 | |
4 | documentation
5 | |
6 | demo
7 | |
8 | community
9 | |
10 | npm package
11 |
12 |
13 | 
14 |
15 | Clay
16 |
17 | [![NPM Package][npm]][npm-url]
18 | [![NPM Package][npm-downloads]][npm-url]
19 |
20 | This library allows to create and edit IFC geometry and data, as well as create modellers for end users. 🚀
21 |
22 | [npm]: https://img.shields.io/npm/v/@thatopen/clay
23 | [npm-url]: https://www.npmjs.com/package/@thatopen/clay
24 | [npm-downloads]: https://img.shields.io/npm/dw/@thatopen/clay
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | If you're developing, append the package and component folder to the URL in order to see the example working without
12 | compiling. For example:
13 |
14 |
15 | To see the compiled example after you run yarn build, append the name of the component at the end of the URL. For
16 | example:
17 |
18 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { InitialOptionsTsJest } from "ts-jest/dist/types";
2 |
3 | const config: InitialOptionsTsJest = {
4 | preset: "ts-jest",
5 | testEnvironment: "jsdom",
6 | transform: {
7 | "^.+\\.(t|j)s$": "ts-jest",
8 | },
9 | globals: {
10 | "ts-jest": {
11 | tsconfig: "./tsconfig.jest.json",
12 | },
13 | },
14 | // These node_modules need to be transpiled
15 | // https://stackoverflow.com/a/63390125/3466729
16 | transformIgnorePatterns: [
17 | "node_modules/(?!(web-ifc-three|web-ifc|three|@popperjs/core/dist/esm))",
18 | ],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "BIM modelling library built on top of the IFC schema.",
3 | "author": "That Open Company",
4 | "contributors": [
5 | "Antonio Gonzalez Viegas (https://github.com/agviegas)",
6 | "Juan Hoyos (https://github.com/HoyosJuan)"
7 | ],
8 | "scripts": {
9 | "dev": "vite --host",
10 | "build-core": "yarn workspace @thatopen/fragments build",
11 | "build-examples": "vite build --config ./vite.config-examples.ts",
12 | "build": "yarn build-examples && yarn build-core",
13 | "test": "echo 'test to be implemented!'",
14 | "publish-repo": "yarn workspace @thatopen/fragments publish-repo",
15 | "reset-release-please": "git commit --allow-empty -m \"chore: release 2.0.0\" -m \"Release-As: 2.0.0\""
16 | },
17 | "license": "MIT",
18 | "homepage": "https://github.com/ThatOpen/engine_clay#readme",
19 | "bugs": {
20 | "url": "https://github.com/ThatOpen/engine_clay/issues"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/ThatOpen/engine_clay.git"
25 | },
26 | "workspaces": [
27 | "./packages/*"
28 | ],
29 | "devDependencies": {
30 | "stats.js": "^0.17.0",
31 | "vite": "5.1.6",
32 | "vite-plugin-dts": "3.7.3"
33 | },
34 | "version": "2.2.0"
35 | }
36 |
--------------------------------------------------------------------------------
/packages/clay/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/packages/clay/README.md
--------------------------------------------------------------------------------
/packages/clay/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@thatopen/clay",
3 | "description": "BIM modelling library built on top of the IFC schema.",
4 | "version": "2.4.0",
5 | "author": "That Open Company",
6 | "contributors": [
7 | "Antonio Gonzalez Viegas (https://github.com/agviegas)",
8 | "Juan Hoyos (https://github.com/HoyosJuan)"
9 | ],
10 | "license": "MIT",
11 | "homepage": "https://docs.thatopen.com/",
12 | "bugs": {
13 | "url": "https://github.com/ThatOpen/engine_clay/issues"
14 | },
15 | "type": "module",
16 | "main": "dist/index.cjs",
17 | "module": "dist/index.mjs",
18 | "types": "dist/index.d.ts",
19 | "files": [
20 | "dist"
21 | ],
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/ThatOpen/engine_clay.git",
25 | "directory": "packages/fragments"
26 | },
27 | "packageManager": "yarn@3.2.1",
28 | "scripts": {
29 | "dev": "vite --host",
30 | "test": "jest",
31 | "build": "tsc --p ./tsconfig-build.json && vite build",
32 | "prepublishOnly": "yarn build",
33 | "publish-repo": "npm publish",
34 | "publish-alpha": "npm publish --tag alpha"
35 | },
36 | "publishConfig": {
37 | "access": "public"
38 | },
39 | "devDependencies": {
40 | "@thatopen/components": "^2.2.11",
41 | "@thatopen/fragments": "2.2.0",
42 | "@thatopen/ui": "^2.2.2",
43 | "@types/earcut": "^2.1.4",
44 | "@types/jest": "27.0.0",
45 | "@types/node": "20.11.30",
46 | "@types/three": "0.160.0",
47 | "@types/uuid": "^10.0.0",
48 | "@typescript-eslint/eslint-plugin": "7.2.0",
49 | "@typescript-eslint/parser": "7.2.0",
50 | "client-zip": "2.3.0",
51 | "eslint": "8.57.0",
52 | "eslint-config-airbnb-base": "15.0.0",
53 | "eslint-config-prettier": "9.1.0",
54 | "eslint-plugin-import": "2.29.1",
55 | "eslint-plugin-prettier": "5.1.3",
56 | "glob": "latest",
57 | "jest": "^27.0.4",
58 | "prettier": "3.2.5",
59 | "stats.js": "^0.17.0",
60 | "three": "^0.160.1",
61 | "ts-jest": "^27.0.3",
62 | "ts-node": "^10.0.0",
63 | "typescript": "5.4.2",
64 | "web-ifc": "0.0.59"
65 | },
66 | "dependencies": {
67 | "earcut": "^3.0.0",
68 | "three-mesh-bvh": "0.7.0",
69 | "uuid": "^10.0.0"
70 | },
71 | "peerDependencies": {
72 | "@thatopen/fragments": "2.2.0",
73 | "three": "^0.160.1",
74 | "web-ifc": "0.0.59"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Elements/DynamicElementType/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { ClayElement } from "../Element";
4 | import { ClayElementType } from "../ElementType";
5 |
6 | /**
7 | * Dynamic variation of {@link ClayElementType}, used in types that need geometry control at the instance level. It's less efficient but more flexible than {@link StaticClayElementType}.
8 | */
9 | export abstract class DynamicClayElementType<
10 | T extends ClayElement,
11 | > extends ClayElementType {
12 | /**
13 | * {@link ClayElementType.attributes}
14 | */
15 | abstract attributes: IFC.IfcElementType;
16 |
17 | /**
18 | * {@link ClayElementType.addInstance}. It creates a new fragment per instance, allowing for geometry control at the instance level.
19 | */
20 | addInstance(): T {
21 | const element = this.createElement();
22 | const id = element.attributes.expressID;
23 | for (const geomID of element.geometries) {
24 | const fragment = this.newFragment();
25 | const colors = [new THREE.Color(1, 1, 1)];
26 | const transforms = [new THREE.Matrix4().identity()];
27 | fragment.add([{ id, colors, transforms }]);
28 | this.fragments.set(geomID, fragment);
29 | }
30 | element.update(true);
31 | this.elements.set(id, element);
32 | return element;
33 | }
34 |
35 | /**
36 | * {@link ClayElementType.addInstance}. Deletes a specific fragment.
37 | */
38 | deleteInstance(id: number) {
39 | const element = this.elements.get(id);
40 | if (!element) {
41 | throw new Error("Element does not exist!");
42 | }
43 | this.model.delete(element.attributes, true);
44 |
45 | for (id of element.geometries) {
46 | const fragment = this.fragments.get(id);
47 | if (!fragment) {
48 | throw new Error("Fragment not found!");
49 | }
50 | fragment.dispose(false);
51 | this.fragments.delete(id);
52 |
53 | const geometry = this.geometries.get(id);
54 | if (!geometry) {
55 | throw new Error("Geometry not found!");
56 | }
57 | geometry.delete();
58 | this.geometries.delete(id);
59 | }
60 | }
61 |
62 | /**
63 | * Updates all the elements of this type.
64 | * @param updateGeometry whether to update the element geometries or not. Remember that in dynamic types, each element has a
65 | */
66 | update(updateGeometry = false) {
67 | for (const [_id, element] of this.elements) {
68 | element.update(updateGeometry);
69 | }
70 | }
71 |
72 | protected abstract createElement(): T;
73 | }
74 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Elements/ElementType/index.ts:
--------------------------------------------------------------------------------
1 | import * as FRAGS from "@thatopen/fragments";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import * as THREE from "three";
4 | import { ClayObject } from "../../Object";
5 | import { ClayGeometry } from "../../Geometry";
6 | import { ClayElement } from "../Element";
7 |
8 | /**
9 | * Base class of all element types in CLAY. In CLAY, types are the managers of instances. In other words: if you want to create a wall, you must first create a wall type, and then use it to create a wall instance. It manages all {@link ClayGeometry}, {@link ClayElement} and {@link FRAGS.Fragment} that belong to this type. It allows to create and delete element instances of this type.
10 | */
11 | export abstract class ClayElementType<
12 | T extends ClayElement = ClayElement,
13 | > extends ClayObject {
14 | /**
15 | * The IFC data of this object type.
16 | */
17 | abstract attributes: IFC.IfcElementType;
18 |
19 | /**
20 | * All {@link ClayGeometry} that belong to elements of this type.
21 | */
22 | geometries = new Map();
23 |
24 | /**
25 | * All {@link ClayElement} of this type.
26 | */
27 | elements = new Map();
28 |
29 | /**
30 | * All {@link FRAGS.Fragment} that belong to elements of this type.
31 | */
32 | fragments = new Map();
33 |
34 | /**
35 | * Adds a new instance of this type.
36 | */
37 | abstract addInstance(): T;
38 |
39 | /**
40 | * Deletes an existing instance of this type.
41 | */
42 | abstract deleteInstance(id: number): void;
43 |
44 | protected newFragment() {
45 | const geometry = new THREE.BufferGeometry();
46 | geometry.setIndex([]);
47 | const fragment = new FRAGS.Fragment(geometry, this.model.material, 0);
48 | fragment.mesh.frustumCulled = false;
49 | return fragment;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Elements/StaticElementType/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import * as THREE from "three";
3 | import { ClayElement } from "../Element";
4 | import { ClayElementType } from "../ElementType";
5 |
6 | /**
7 | * Static variation of {@link ClayElementType}, used in types that need geometry control at the type level. It's more efficient but less flexible than {@link StaticClayElementType}.
8 | */
9 | export abstract class StaticClayElementType<
10 | T extends ClayElement,
11 | > extends ClayElementType {
12 | /**
13 | * {@link ClayElementType.attributes}
14 | */
15 | abstract attributes: IFC.IfcElementType;
16 |
17 | /**
18 | * The IFC data containing the geometries of this type (remember that all elements of static types share the same geometry).
19 | */
20 | abstract shape: IFC.IfcProductDefinitionShape;
21 |
22 | /**
23 | * {@link ClayElementType.addInstance}. It creates a new instance to the fragments shared by all elements.
24 | */
25 | addInstance(): T {
26 | const element = this.createElement();
27 | const id = element.attributes.expressID;
28 | this.elements.set(id, element);
29 |
30 | for (const [_geometryID, fragment] of this.fragments) {
31 | const colors = [new THREE.Color(1, 1, 1)];
32 | const transforms = [new THREE.Matrix4().identity()];
33 | fragment.add([{ id, colors, transforms }]);
34 | }
35 |
36 | element.update(true);
37 |
38 | return element;
39 | }
40 |
41 | /**
42 | * {@link ClayElementType.addInstance}. Deletes a specific instance in the shared fragments.
43 | */
44 | deleteInstance(id: number) {
45 | const element = this.elements.get(id);
46 | if (!element) {
47 | throw new Error("Element does not exist!");
48 | }
49 | element.attributes.Representation = null;
50 | this.model.set(element.attributes);
51 | this.model.delete(element.attributes, true);
52 |
53 | for (const [_geometryID, fragment] of this.fragments) {
54 | fragment.remove([id]);
55 | }
56 | }
57 |
58 | /**
59 | * Updates all the elements of this type.
60 | * @param updateGeometry whether to update the element geometries or not. Remember that in static types, all elements share the same geometries.
61 | */
62 | update(updateGeometry = false) {
63 | let first = updateGeometry;
64 | for (const [_id, element] of this.elements) {
65 | // Geometry is shared, so only update it in first instance
66 | element.update(first);
67 | first = false;
68 | }
69 | }
70 |
71 | protected abstract createElement(): T;
72 | }
73 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Elements/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ElementType";
2 | export * from "./Element";
3 | export * from "./StaticElementType";
4 | export * from "./DynamicElementType";
5 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Event/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple event handler by [Jason Kleban](https://gist.github.com/JasonKleban/50cee44960c225ac1993c922563aa540). Keep in mind that if you want to remove it later, you might want to declare the callback as an object. If you want to maintain the reference to `this`, you will need to declare the callback as an arrow function.
3 | */
4 | export class Event {
5 | /**
6 | * Add a callback to this event instance.
7 | * @param handler - the callback to be added to this event.
8 | */
9 | add(handler: T extends void ? { (): void } : { (data: T): void }): void {
10 | this.handlers.push(handler);
11 | }
12 |
13 | /**
14 | * Removes a callback from this event instance.
15 | * @param handler - the callback to be removed from this event.
16 | */
17 | remove(handler: T extends void ? { (): void } : { (data: T): void }): void {
18 | this.handlers = this.handlers.filter((h) => h !== handler);
19 | }
20 |
21 | /** Triggers all the callbacks assigned to this event. */
22 | trigger = (data?: T) => {
23 | const handlers = this.handlers.slice(0);
24 | for (const handler of handlers) {
25 | handler(data as any);
26 | }
27 | };
28 |
29 | /** Gets rid of all the suscribed events. */
30 | reset() {
31 | this.handlers.length = 0;
32 | }
33 |
34 | private handlers: (T extends void ? { (): void } : { (data: T): void })[] =
35 | [];
36 | }
37 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Geometry/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { ClayObject } from "../Object";
3 | import { ClayObject3D } from "../Object3D";
4 |
5 | /**
6 | * An object that represents an IFC geometry that can represent one or many IfcElements in 3D. It supports boolean operations.
7 | */
8 | export abstract class ClayGeometry extends ClayObject3D {
9 | /**
10 | * {@link ClayObject.attributes}. It can either be an IFC geometry, or the result of a boolean operation.
11 | */
12 | abstract attributes:
13 | | IFC.IfcGeometricRepresentationItem
14 | | IFC.IfcBooleanClippingResult;
15 |
16 | /**
17 | * The base IFC geometry of this object. If there are no boolean operations, it's the same as {@link ClayGeometry.attributes}.
18 | */
19 | abstract core: IFC.IfcGeometricRepresentationItem;
20 |
21 | protected clippings = new Map<
22 | number,
23 | {
24 | previous: number | null;
25 | next: number | null;
26 | bool: IFC.IfcBooleanClippingResult;
27 | }
28 | >();
29 |
30 | protected firstClipping: number | null = null;
31 |
32 | protected lastClipping: number | null = null;
33 |
34 | protected subtractedGeometriesToBools = new Map();
35 |
36 | /**
37 | * Deletes this geometry in the IFC model.
38 | */
39 | delete() {
40 | this.model.delete(this.core);
41 | for (const [, { bool }] of this.clippings) {
42 | this.model.delete(bool);
43 | }
44 | this.clippings.clear();
45 | this.subtractedGeometriesToBools.clear();
46 | }
47 |
48 | /**
49 | * Adds a boolean subtraction to this geometry.
50 | * @param geometry the geometry to subtract from this.
51 | */
52 | addSubtraction(geometry: ClayGeometry) {
53 | const item = geometry.attributes;
54 |
55 | if (this.clippings.has(item.expressID)) {
56 | return;
57 | }
58 |
59 | // Create bool between the given item and the current geometry
60 | // (might be another bool operation)
61 | const bool = new IFC.IfcBooleanClippingResult(
62 | IFC.IfcBooleanOperator.DIFFERENCE,
63 | this.attributes,
64 | item,
65 | );
66 |
67 | this.model.set(bool);
68 |
69 | // If it's the first clipping, reference it
70 | const isFirstClipping = this.clippings.size === 0;
71 | if (isFirstClipping) {
72 | this.firstClipping = item.expressID;
73 | }
74 |
75 | // Reference this clipping by last one (if any)
76 | if (this.lastClipping) {
77 | const lastBool = this.clippings.get(this.lastClipping);
78 | if (!lastBool) {
79 | throw new Error("Malformed bool structure!");
80 | }
81 | lastBool.next = bool.expressID;
82 | }
83 |
84 | // Add clipping to the list
85 | const previous = this.lastClipping;
86 | this.clippings.set(item.expressID, { bool, previous, next: null });
87 |
88 | // Make this bool the current geometry
89 |
90 | this.attributes = bool;
91 | this.update();
92 |
93 | this.subtractedGeometriesToBools.set(
94 | geometry.attributes.expressID,
95 | bool.expressID,
96 | );
97 | }
98 |
99 | /**
100 | * Remove the specified geometry as subtraction (if it is a subtraction).
101 | * @param geometry the geometry whose subtraction to remove. If this geometry is not a subtraction, nothing happens.
102 | */
103 | removeSubtraction(geometry: ClayGeometry) {
104 | const boolID = this.subtractedGeometriesToBools.get(
105 | geometry.attributes.expressID,
106 | );
107 |
108 | if (boolID === undefined) {
109 | // This geometry was not subtracted from this one
110 | return;
111 | }
112 |
113 | const found = this.clippings.get(boolID);
114 | if (!found) {
115 | return;
116 | }
117 |
118 | const { bool, next, previous } = found;
119 |
120 | if (previous === null && next === null) {
121 | // This was the only bool in the list
122 | this.attributes = this.core;
123 | this.firstClipping = null;
124 | this.lastClipping = null;
125 | } else if (previous !== null && next === null) {
126 | // The deleted bool was the last one in the list
127 | const newLast = this.clippings.get(previous);
128 | if (!newLast) {
129 | throw new Error("Malformed bool structure!");
130 | }
131 | newLast.next = null;
132 | this.attributes = newLast.bool;
133 | } else if (previous === null && next !== null) {
134 | // The deleted bool was the first one in the list
135 | const newFirst = this.clippings.get(next);
136 | if (!newFirst) {
137 | throw new Error("Malformed bool structure!");
138 | }
139 | this.firstClipping = next;
140 | newFirst.previous = null;
141 | newFirst.bool.FirstOperand = this.core;
142 | this.model.set(newFirst.bool);
143 | } else if (previous !== null && next !== null) {
144 | // The deleted bool is in the middle of the list
145 | const before = this.clippings.get(next);
146 | const after = this.clippings.get(previous);
147 | if (!before || !after) {
148 | throw new Error("Malformed bool structure!");
149 | }
150 | before.next = next;
151 | after.previous = previous;
152 | before.bool.SecondOperand = after.bool;
153 | after.bool.FirstOperand = before.bool;
154 | this.model.set(before.bool);
155 | this.model.set(after.bool);
156 | }
157 |
158 | // Remove bool
159 | this.clippings.delete(boolID);
160 | this.model.delete(bool);
161 | this.update();
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Model/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import * as WEBIFC from "web-ifc";
3 | import { ClayElementType } from "../Elements";
4 |
5 | /**
6 | * Object that represents an IFC model and manages all its data.
7 | */
8 | export class Model {
9 | wasm = {
10 | path: "",
11 | absolute: false,
12 | };
13 |
14 | settings: WEBIFC.LoaderSettings = {
15 | TAPE_SIZE: 5000000, // 5MB
16 | };
17 |
18 | /**
19 | * Opaque material of the model. All models have just 1 shared opaque and transparent material.
20 | */
21 | material = new THREE.MeshLambertMaterial();
22 |
23 | /**
24 | * Transparent material of the model. All models have just 1 shared opaque and transparent material.
25 | */
26 | materialT = new THREE.MeshLambertMaterial({
27 | transparent: true,
28 | opacity: 0.2,
29 | });
30 |
31 | /**
32 | * Types created within this model.
33 | */
34 | types = new Map();
35 |
36 | private _ifcAPI = new WEBIFC.IfcAPI();
37 |
38 | private _context?: WEBIFC.IFC4X3.IfcRepresentationContext;
39 |
40 | private _modelID?: number;
41 |
42 | /**
43 | * The core of our libraries. It contains our IFC parser and geometry engine.
44 | */
45 | get ifcAPI() {
46 | return this._ifcAPI;
47 | }
48 |
49 | /**
50 | * The ID that identifies this IFC file.
51 | */
52 | get modelID() {
53 | if (this._modelID === undefined) {
54 | throw new Error("Model not initialized! Call the init() method.");
55 | }
56 | return this._modelID;
57 | }
58 |
59 | /**
60 | * The IFC context of this IFC file.
61 | */
62 | get context() {
63 | if (this._context === undefined) {
64 | throw new Error("Model not initialized! Call the init() method.");
65 | }
66 | return this._context;
67 | }
68 |
69 | /**
70 | * Initializes the library, allowing it to create and edit IFC data.
71 | */
72 | async init() {
73 | this._ifcAPI.SetWasmPath(this.wasm.path, this.wasm.absolute);
74 | await this._ifcAPI.Init();
75 | this._modelID = this._ifcAPI.CreateModel(
76 | { schema: WEBIFC.Schemas.IFC4X3 },
77 | this.settings,
78 | );
79 | this._context = new WEBIFC.IFC4X3.IfcRepresentationContext(
80 | new WEBIFC.IFC4X3.IfcLabel("Default"),
81 | new WEBIFC.IFC4X3.IfcLabel("Model"),
82 | );
83 | }
84 |
85 | /**
86 | * Creates or overwrites an item in the IFC file.
87 | * @param item the object to create or override.
88 | */
89 | set(item: WEBIFC.IfcLineObject) {
90 | this._ifcAPI.WriteLine(this.modelID, item);
91 | }
92 |
93 | /**
94 | * Deletes an item in the IFC file, and (optionally) all the items it references.
95 | * @param item the object to delete.
96 | * @param recursive whether to delete all items referenced by this item as well.
97 | */
98 | delete(
99 | item: WEBIFC.IfcLineObject | WEBIFC.Handle | null,
100 | recursive = false,
101 | ) {
102 | if (item === null) {
103 | return;
104 | }
105 |
106 | let foundItem: WEBIFC.IfcLineObject;
107 | if (item instanceof WEBIFC.Handle) {
108 | foundItem = this._ifcAPI.GetLine(this.modelID, item.value);
109 | } else {
110 | foundItem = item;
111 | }
112 |
113 | if (recursive) {
114 | for (const key in foundItem) {
115 | // @ts-ignore
116 | const value = foundItem[key];
117 | if (value instanceof WEBIFC.Handle) {
118 | this.delete(value);
119 | }
120 | }
121 | }
122 |
123 | this._ifcAPI.DeleteLine(this.modelID, foundItem.expressID);
124 | }
125 |
126 | /**
127 | * Gets an object data.
128 | * @param item the object whose data to get.
129 | */
130 | get(item: WEBIFC.Handle | T | null) {
131 | if (item === null) {
132 | throw new Error("Item not found!");
133 | }
134 | if (item instanceof WEBIFC.Handle) {
135 | return this._ifcAPI.GetLine(this.modelID, item.value) as T;
136 | }
137 | return item;
138 | }
139 |
140 | /**
141 | * Updates a model. Necessary for applying new boolean operations.
142 | */
143 | async update() {
144 | if (this._modelID === undefined) {
145 | throw new Error("Malformed model!");
146 | }
147 | // TODO: Fix memory leak
148 | const model = this._ifcAPI.SaveModel(this._modelID);
149 |
150 | this._ifcAPI.Dispose();
151 | this._ifcAPI = null as any;
152 | this._ifcAPI = new WEBIFC.IfcAPI();
153 |
154 | await this.init();
155 | this._modelID = this._ifcAPI.OpenModel(model, this.settings);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Object/index.ts:
--------------------------------------------------------------------------------
1 | import * as WEBIFC from "web-ifc";
2 | import { Model } from "../Model";
3 |
4 | /**
5 | * Base object for all Clay classes.
6 | */
7 | export abstract class ClayObject {
8 | /**
9 | * The IFC model this object belongs to.
10 | */
11 | model: Model;
12 |
13 | /**
14 | * The IFC data of this object.
15 | */
16 | abstract attributes: WEBIFC.IfcLineObject;
17 |
18 | /**
19 | * Update this object by overriding its data in the IFC model.
20 | */
21 | abstract update(): void;
22 |
23 | protected constructor(model: Model) {
24 | this.model = model;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/clay/src/core/Object3D/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { ClayObject } from "../Object";
3 |
4 | /**
5 | * Base object for all Clay objects with a transformation in 3D space.
6 | */
7 | export abstract class ClayObject3D extends ClayObject {
8 | /**
9 | * Object representing the transformation of the object in 3D space.
10 | */
11 | transformation = new THREE.Object3D();
12 | }
13 |
--------------------------------------------------------------------------------
/packages/clay/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Model";
2 | export * from "./Elements";
3 | export * from "./Geometry";
4 | export * from "./Object";
5 | export * from "./Object3D";
6 | export * from "./Event";
7 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/CurtainWalls/SimpleCurtainWall/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/CurtainWalls/SimpleCurtainWall/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { Model, ClayElement } from "../../../../core";
4 | import { IfcUtils } from "../../../../utils/ifc-utils";
5 |
6 | import { SimpleCurtainWallType } from "../index";
7 |
8 | export class SimpleCurtainWall extends ClayElement {
9 | attributes: IFC.IfcCurtainWall;
10 |
11 | type: SimpleCurtainWallType;
12 |
13 | constructor(model: Model, type: SimpleCurtainWallType) {
14 | super(model, type);
15 | this.type = type;
16 |
17 | const placement = IfcUtils.localPlacement();
18 |
19 | for (const [id] of type.geometries) {
20 | this.geometries.add(id);
21 | }
22 |
23 | this.attributes = new IFC.IfcCurtainWall(
24 | new IFC.IfcGloballyUniqueId(uuidv4()),
25 | null,
26 | null,
27 | null,
28 | null,
29 | placement,
30 | type.shape,
31 | null,
32 | null,
33 | );
34 |
35 | this.model.set(this.attributes);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/CurtainWalls/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleCurtainWall";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Furniture/SimpleFurniture/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Furniture/SimpleFurniture/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { Model, StaticClayElementType } from "../../../core";
4 |
5 | import { SimpleFurniture } from "./src";
6 | import { Brep } from "../../../geometries";
7 | import { IfcUtils } from "../../../utils/ifc-utils";
8 |
9 | export * from "./src";
10 |
11 | export class SimpleFurnitureType extends StaticClayElementType {
12 | attributes: IFC.IfcFurnishingElementType;
13 |
14 | shape: IFC.IfcProductDefinitionShape;
15 |
16 | body: Brep;
17 |
18 | constructor(model: Model) {
19 | super(model);
20 |
21 | this.body = new Brep(model);
22 | const id = this.body.attributes.expressID;
23 | this.geometries.set(id, this.body);
24 | this.shape = IfcUtils.productDefinitionShape(model, [this.body.attributes]);
25 |
26 | const fragment = this.newFragment();
27 | this.fragments.set(id, fragment);
28 |
29 | this.attributes = new IFC.IfcFurnishingElementType(
30 | new IFC.IfcGloballyUniqueId(uuidv4()),
31 | null,
32 | null,
33 | null,
34 | null,
35 | null,
36 | null,
37 | null,
38 | null,
39 | );
40 | }
41 |
42 | protected createElement() {
43 | return new SimpleFurniture(this.model, this);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Furniture/SimpleFurniture/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { Model, ClayElement } from "../../../../core";
4 | import { IfcUtils } from "../../../../utils/ifc-utils";
5 |
6 | import { SimpleFurnitureType } from "../index";
7 |
8 | export class SimpleFurniture extends ClayElement {
9 | attributes: IFC.IfcFurnishingElement;
10 |
11 | type: SimpleFurnitureType;
12 |
13 | constructor(model: Model, type: SimpleFurnitureType) {
14 | super(model, type);
15 | this.type = type;
16 |
17 | const placement = IfcUtils.localPlacement();
18 |
19 | this.geometries.add(type.body.attributes.expressID);
20 |
21 | this.attributes = new IFC.IfcFurnishingElement(
22 | new IFC.IfcGloballyUniqueId(uuidv4()),
23 | null,
24 | null,
25 | null,
26 | null,
27 | placement,
28 | type.shape,
29 | null,
30 | );
31 |
32 | this.model.set(this.attributes);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Furniture/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleFurniture";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Members/SimpleMember/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Members/SimpleMember/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Model, DynamicClayElementType } from "../../../core";
4 |
5 | import { SimpleMember } from "./src";
6 |
7 | export class SimpleMemberType extends DynamicClayElementType {
8 | attributes: IFC.IfcMemberType;
9 |
10 | memberType: IFC.IfcMemberTypeEnum;
11 |
12 | constructor(model: Model) {
13 | super(model);
14 |
15 | this.memberType = IFC.IfcMemberTypeEnum.MULLION;
16 |
17 | this.attributes = new IFC.IfcMemberType(
18 | new IFC.IfcGloballyUniqueId(uuidv4()),
19 | null,
20 | null,
21 | null,
22 | null,
23 | null,
24 | null,
25 | null,
26 | null,
27 | this.memberType,
28 | );
29 | }
30 |
31 | protected createElement() {
32 | return new SimpleMember(this.model, this);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Members/SimpleMember/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import * as THREE from "three";
4 | import { ClayElement, Model } from "../../../../core";
5 |
6 | import { SimpleMemberType } from "..";
7 | import { IfcUtils } from "../../../../utils/ifc-utils";
8 | import { Extrusion, RectangleProfile } from "../../../../geometries";
9 |
10 | export class SimpleMember extends ClayElement {
11 | attributes: IFC.IfcMember;
12 |
13 | body: Extrusion;
14 |
15 | type: SimpleMemberType;
16 |
17 | width = 0.0635;
18 |
19 | depth = 0.127;
20 |
21 | constructor(model: Model, type: SimpleMemberType) {
22 | super(model, type);
23 | this.type = type;
24 | const location = new THREE.Vector3(0, 0, 1);
25 | const placement = IfcUtils.localPlacement(location);
26 |
27 | const profile = new RectangleProfile(model);
28 | profile.dimension.x = this.depth;
29 | profile.dimension.y = this.width;
30 | profile.transformation.position.set(0, 0, 5);
31 | profile.update();
32 |
33 | this.body = new Extrusion(model, profile);
34 | const id = this.body.attributes.expressID;
35 | this.type.geometries.set(id, this.body);
36 | this.geometries.add(id);
37 |
38 | this.attributes = new IFC.IfcPlate(
39 | new IFC.IfcGloballyUniqueId(uuidv4()),
40 | null,
41 | null,
42 | null,
43 | null,
44 | placement,
45 | IfcUtils.productDefinitionShape(model, [this.body.attributes]),
46 | null,
47 | type.memberType,
48 | );
49 | this.model.set(this.attributes);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Members/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleMember";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Openings/SimpleOpening/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Openings/SimpleOpening/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3, IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { StaticClayElementType, Model } from "../../../core";
4 | import { SimpleOpening } from "./src";
5 | import { Extrusion, RectangleProfile } from "../../../geometries";
6 |
7 | import { IfcUtils } from "../../../utils/ifc-utils";
8 |
9 | export * from "./src";
10 |
11 | export class SimpleOpeningType extends StaticClayElementType {
12 | attributes: IFC4X3.IfcElementType;
13 |
14 | shape: IFC4X3.IfcProductDefinitionShape;
15 |
16 | height = 1;
17 |
18 | width = 1;
19 |
20 | length = 1;
21 |
22 | get body() {
23 | const geoms = this.geometries.values();
24 | return geoms.next().value as Extrusion;
25 | }
26 |
27 | constructor(model: Model) {
28 | super(model);
29 |
30 | const profile = new RectangleProfile(model);
31 | const body = new Extrusion(model, profile);
32 | const id = body.attributes.expressID;
33 | this.geometries.set(id, body);
34 | this.shape = IfcUtils.productDefinitionShape(model, [body.attributes]);
35 |
36 | const fragment = this.newFragment();
37 | fragment.mesh.material = [this.model.materialT];
38 | this.fragments.set(id, fragment);
39 |
40 | this.attributes = new IFC.IfcElementType(
41 | new IFC.IfcGloballyUniqueId(uuidv4()),
42 | null,
43 | null,
44 | null,
45 | null,
46 | null,
47 | null,
48 | null,
49 | null,
50 | );
51 |
52 | this.model.set(this.attributes);
53 | }
54 |
55 | update(updateGeometry: boolean = false) {
56 | this.body.profile.dimension.x = this.width;
57 | this.body.profile.dimension.y = this.length;
58 | this.body.profile.update();
59 | this.body.depth = this.height;
60 | this.body.update();
61 |
62 | super.update(updateGeometry);
63 | }
64 |
65 | protected createElement() {
66 | return new SimpleOpening(this.model, this);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Openings/SimpleOpening/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { ClayElement, Model } from "../../../../core";
4 | import { SimpleOpeningType } from "../index";
5 |
6 | import { IfcUtils } from "../../../../utils/ifc-utils";
7 |
8 | export class SimpleOpening extends ClayElement {
9 | attributes: IFC.IfcOpeningElement;
10 |
11 | type: SimpleOpeningType;
12 |
13 | constructor(model: Model, type: SimpleOpeningType) {
14 | super(model, type);
15 | this.type = type;
16 |
17 | const placement = IfcUtils.localPlacement();
18 |
19 | this.geometries.add(type.body.attributes.expressID);
20 |
21 | this.attributes = new IFC.IfcOpeningElement(
22 | new IFC.IfcGloballyUniqueId(uuidv4()),
23 | null,
24 | null,
25 | null,
26 | null,
27 | placement,
28 | type.shape,
29 | null,
30 | null,
31 | );
32 |
33 | this.model.set(this.attributes);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Openings/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleOpening";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Plates/SimplePlate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
15 |
20 | Tools Component
21 |
34 |
35 |
36 |
37 |
50 |
51 |
52 |
107 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Plates/SimplePlate/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Model, DynamicClayElementType } from "../../../core";
4 |
5 | import { SimplePlate } from "./src";
6 |
7 | export class SimplePlateType extends DynamicClayElementType {
8 | attributes: IFC.IfcPlateType;
9 |
10 | plateType: IFC.IfcPlateTypeEnum;
11 |
12 | constructor(model: Model) {
13 | super(model);
14 |
15 | this.plateType = IFC.IfcPlateTypeEnum.CURTAIN_PANEL;
16 |
17 | this.attributes = new IFC.IfcPlateType(
18 | new IFC.IfcGloballyUniqueId(uuidv4()),
19 | null,
20 | null,
21 | null,
22 | null,
23 | null,
24 | null,
25 | null,
26 | null,
27 | this.plateType,
28 | );
29 | }
30 |
31 | protected createElement() {
32 | return new SimplePlate(this.model, this);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Plates/SimplePlate/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { ClayElement, Model } from "../../../../core";
4 |
5 | import { SimplePlateType } from "..";
6 | import { IfcUtils } from "../../../../utils/ifc-utils";
7 | import { Extrusion, RectangleProfile } from "../../../../geometries";
8 |
9 | export class SimplePlate extends ClayElement {
10 | attributes: IFC.IfcPlate;
11 |
12 | body: Extrusion;
13 |
14 | type: SimplePlateType;
15 |
16 | constructor(model: Model, type: SimplePlateType) {
17 | super(model, type);
18 | this.type = type;
19 |
20 | const placement = IfcUtils.localPlacement();
21 |
22 | const profile = new RectangleProfile(model);
23 | profile.dimension.x = 0.0833333333333333;
24 | profile.dimension.y = 1.9238;
25 | profile.dimension.z = 1;
26 | profile.transformation.position.set(0, 0, 0);
27 | profile.update();
28 |
29 | this.body = new Extrusion(model, profile);
30 | const id = this.body.attributes.expressID;
31 | this.type.geometries.set(id, this.body);
32 | this.geometries.add(id);
33 |
34 | this.attributes = new IFC.IfcPlate(
35 | new IFC.IfcGloballyUniqueId(uuidv4()),
36 | null,
37 | null,
38 | null,
39 | null,
40 | placement,
41 | IfcUtils.productDefinitionShape(model, [this.body.attributes]),
42 | null,
43 | type.plateType,
44 | );
45 |
46 | this.model.set(this.attributes);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Plates/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimplePlate";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Slabs/SimpleSlab/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Slabs/SimpleSlab/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { IFC4X3, IFC4X3 as IFC } from "web-ifc";
3 | import { Model, DynamicClayElementType } from "../../../core";
4 |
5 | import { SimpleSlab } from "./src";
6 |
7 | export * from "./src";
8 |
9 | export class SimpleSlabType extends DynamicClayElementType {
10 | attributes: IFC4X3.IfcSlabType;
11 |
12 | constructor(model: Model) {
13 | super(model);
14 |
15 | this.attributes = new IFC.IfcSlabType(
16 | new IFC.IfcGloballyUniqueId(uuidv4()),
17 | null,
18 | null,
19 | null,
20 | null,
21 | null,
22 | null,
23 | null,
24 | null,
25 | IFC.IfcSlabTypeEnum.FLOOR,
26 | );
27 | }
28 |
29 | protected createElement() {
30 | return new SimpleSlab(this.model, this);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Slabs/SimpleSlab/src/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Model, ClayElement } from "../../../../core";
4 | import { IfcUtils } from "../../../../utils/ifc-utils";
5 |
6 | import { SimpleSlabType } from "../index";
7 | import { Extrusion } from "../../../../geometries";
8 | import { ArbitraryClosedProfile } from "../../../../geometries/Profiles/ArbitraryClosedProfile";
9 |
10 | export class SimpleSlab extends ClayElement {
11 | attributes: IFC.IfcSlab;
12 |
13 | type: SimpleSlabType;
14 |
15 | body: Extrusion;
16 |
17 | thickness = 0.3;
18 |
19 | constructor(model: Model, type: SimpleSlabType) {
20 | super(model, type);
21 | this.type = type;
22 |
23 | this.body = new Extrusion(model, new ArbitraryClosedProfile(model));
24 | const id = this.body.attributes.expressID;
25 | this.type.geometries.set(id, this.body);
26 | this.geometries.add(id);
27 |
28 | const placement = IfcUtils.localPlacement();
29 | const shape = IfcUtils.productDefinitionShape(model, [
30 | this.body.attributes,
31 | ]);
32 |
33 | this.attributes = new IFC.IfcSlab(
34 | new IFC.IfcGloballyUniqueId(uuidv4()),
35 | null,
36 | null,
37 | null,
38 | null,
39 | placement,
40 | shape,
41 | null,
42 | null,
43 | );
44 |
45 | this.model.set(this.attributes);
46 | }
47 |
48 | update(updateGeometry: boolean = false) {
49 | this.body.depth = this.thickness;
50 | this.body.update();
51 | super.update(updateGeometry);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Slabs/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleSlab";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 | Simple Walls
13 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Model, DynamicClayElementType } from "../../../core";
4 | import { SimpleWall } from "./src";
5 | import {
6 | SimpleWallCornerer,
7 | WallCornerConfig,
8 | } from "./src/simple-wall-cornerer";
9 |
10 | export * from "./src";
11 |
12 | export class SimpleWallType extends DynamicClayElementType {
13 | attributes: IFC.IfcWallType;
14 |
15 | width = 0.2;
16 |
17 | private _cornerer = new SimpleWallCornerer();
18 |
19 | constructor(model: Model) {
20 | super(model);
21 |
22 | this.attributes = new IFC.IfcWallType(
23 | new IFC.IfcGloballyUniqueId(uuidv4()),
24 | null,
25 | null,
26 | null,
27 | null,
28 | null,
29 | null,
30 | null,
31 | null,
32 | IFC.IfcWallTypeEnum.STANDARD,
33 | );
34 |
35 | this.model.set(this.attributes);
36 | }
37 |
38 | addCorner(config: WallCornerConfig) {
39 | this._cornerer.add(config);
40 | }
41 |
42 | updateCorners(ids?: Iterable) {
43 | this._cornerer.update(ids);
44 | }
45 |
46 | protected createElement() {
47 | return new SimpleWall(this.model, this);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./simple-wall";
2 | // export * from "./simple-wall-nester";
3 | // export * from "./simple-wall-corners";
4 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/src/simple-wall-cornerer.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { SimpleWall, WallEndPointType, WallPlaneType } from "./simple-wall";
3 | import { HalfSpace } from "../../../../geometries";
4 |
5 | export interface WallCornerConfig {
6 | wall1: SimpleWall;
7 | wall2: SimpleWall;
8 | to?: WallPlaneType;
9 | priority?: WallEndPointType;
10 | cut?: "exterior" | "interior";
11 | cutDirection?: "exterior" | "interior";
12 | offset?: number | "auto";
13 | }
14 |
15 | interface WallCorner extends WallCornerConfig {
16 | to: WallPlaneType;
17 | priority: WallEndPointType;
18 | offset: number | "auto";
19 | halfSpace?: HalfSpace;
20 | }
21 |
22 | export class SimpleWallCornerer {
23 | list = new Map>();
24 |
25 | private _temp = new THREE.Object3D();
26 |
27 | add(config: WallCornerConfig) {
28 | const id1 = config.wall1.attributes.expressID;
29 | const id2 = config.wall2.attributes.expressID;
30 |
31 | if (!this.list.has(id1)) {
32 | this.list.set(id1, new Map());
33 | }
34 |
35 | const corners = this.list.get(id1) as Map;
36 |
37 | const to = config.to || "center";
38 | const priority = config.priority || "end";
39 | const offset = config.offset || "auto";
40 | corners.set(id2, { ...config, to, priority, offset });
41 | }
42 |
43 | update(ids: Iterable = this.list.keys()) {
44 | for (const id of ids) {
45 | const corners = this.list.get(id);
46 | if (!corners) {
47 | continue;
48 | }
49 | for (const [, corner] of corners) {
50 | this.extend(corner);
51 | }
52 | }
53 | }
54 |
55 | extend(corner: WallCorner) {
56 | const { wall1, wall2, to, priority, offset } = corner;
57 | // Strategy: there are 2 cases
58 | // A) Both points of the wall are on one side of this wall
59 | // In this case, extend its closest point to this wall
60 | // B) Each point of the wall are on one side of this wall
61 | // In that case, keep the point specified in priority
62 |
63 | wall1.transformation.updateMatrix();
64 |
65 | const dir1 = wall1.direction;
66 | const dir2 = wall2.direction;
67 | if (dir1.equals(dir2)) {
68 | // Same direction, so walls can't intersect
69 | return;
70 | }
71 |
72 | const { plane, p1 } = wall1.getPlane(to);
73 | if (plane === null) {
74 | // Malformed wall (e.g. zero length)
75 | return;
76 | }
77 |
78 | const start = wall2.startPoint3D;
79 | const end = wall2.endPoint3D;
80 |
81 | const extendStart = priority === "start";
82 |
83 | const pointToExtend = extendStart ? start : end;
84 | if (plane.distanceToPoint(pointToExtend) !== 0) {
85 | // Point is already aligned with wall
86 | const origin = extendStart ? end : start;
87 | const direction = extendStart ? start : end;
88 | direction.sub(origin);
89 |
90 | const ray = new THREE.Ray(origin, direction);
91 |
92 | const intersection = ray.intersectPlane(plane, new THREE.Vector3());
93 |
94 | if (intersection === null) {
95 | return;
96 | }
97 |
98 | const offsetDist = offset === "auto" ? wall2.type.width : offset;
99 | const factor = extendStart ? -1 : 1;
100 | const offsetVec = dir2.multiplyScalar(offsetDist * factor);
101 | intersection.add(offsetVec);
102 |
103 | const extended = extendStart ? wall2.startPoint : wall2.endPoint;
104 | extended.x = intersection.x;
105 | extended.y = intersection.z;
106 |
107 | wall2.update();
108 | }
109 |
110 | if (corner.cut && corner.cutDirection) {
111 | if (!corner.halfSpace) {
112 | const halfSpace = new HalfSpace(wall1.model);
113 | corner.halfSpace = halfSpace;
114 | wall2.body.addSubtraction(halfSpace);
115 | }
116 |
117 | const halfSpace = corner.halfSpace as HalfSpace;
118 |
119 | const rotation = new THREE.Vector3(0, 0, 1);
120 |
121 | this._temp.position.copy(p1);
122 | const factor = corner.cutDirection === "interior" ? -1 : 1;
123 | const minusNormal = plane.normal.clone().multiplyScalar(factor);
124 | this._temp.lookAt(p1.clone().add(minusNormal));
125 | this._temp.updateMatrix();
126 |
127 | const planeRotation = new THREE.Matrix4();
128 | planeRotation.extractRotation(this._temp.matrix);
129 | rotation.applyMatrix4(planeRotation);
130 |
131 | wall2.transformation.updateMatrix();
132 | const wallRotation = new THREE.Matrix4();
133 | const inverseWall = wall2.transformation.matrix.clone();
134 | inverseWall.invert();
135 | wallRotation.extractRotation(inverseWall);
136 | rotation.applyMatrix4(wallRotation);
137 |
138 | const position = this._temp.position.clone();
139 | position.applyMatrix4(inverseWall);
140 |
141 | halfSpace.transformation.position.copy(position);
142 | halfSpace.direction.copy(rotation).normalize();
143 |
144 | halfSpace.update();
145 | }
146 |
147 | wall2.update(true);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/src/simple-wall-nester.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { ClayElement } from "../../../../core";
3 | import { SimpleWall } from "./simple-wall";
4 |
5 | export class SimpleWallNester {
6 | list = new Map();
7 |
8 | private _wall: SimpleWall;
9 |
10 | constructor(wall: SimpleWall) {
11 | this._wall = wall;
12 | }
13 |
14 | add(element: ClayElement) {
15 | const wallPlane = new THREE.Plane();
16 |
17 | const tempPoint = this._wall.startPoint3D;
18 | tempPoint.y += 1;
19 |
20 | wallPlane.setFromCoplanarPoints(
21 | tempPoint,
22 | this._wall.startPoint3D,
23 | this._wall.endPoint3D,
24 | );
25 |
26 | const newPosition = new THREE.Vector3();
27 | wallPlane.projectPoint(element.transformation.position, newPosition);
28 |
29 | element.transformation.position.copy(newPosition);
30 | element.update();
31 |
32 | // The distance is signed, so that it also supports openings that are
33 | // before the startPoint by using the dot product
34 | let distance = newPosition.distanceTo(this._wall.startPoint3D);
35 | const vector = new THREE.Vector3();
36 | vector.subVectors(newPosition, this._wall.startPoint3D);
37 | const dotProduct = vector.dot(this._wall.direction);
38 | distance *= dotProduct > 0 ? 1 : -1;
39 |
40 | const id = element.attributes.expressID;
41 |
42 | this.list.set(id, { opening: element, distance });
43 | }
44 |
45 | delete(element: ClayElement) {
46 | this.list.delete(element.attributes.expressID);
47 | }
48 |
49 | update() {
50 | const start = this._wall.startPoint3D;
51 | const dir = this._wall.direction;
52 | for (const [_id, { opening, distance }] of this.list) {
53 | const pos = dir.clone().multiplyScalar(distance).add(start);
54 |
55 | // Align opening to wall
56 | opening.transformation.position.x = pos.x;
57 | opening.transformation.position.z = pos.z;
58 | opening.transformation.rotation.y = this._wall.transformation.rotation.y;
59 |
60 | opening.update();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/SimpleWall/src/simple-wall.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { IFC4X3 as IFC } from "web-ifc";
4 | import { Model, ClayElement, Event } from "../../../../core";
5 | import { IfcUtils } from "../../../../utils/ifc-utils";
6 |
7 | import { Extrusion, RectangleProfile } from "../../../../geometries";
8 | import { SimpleWallType } from "../index";
9 | import { SimpleWallNester } from "./simple-wall-nester";
10 |
11 | export type WallPlaneType = "center" | "exterior" | "interior";
12 | export type WallEndPointType = "start" | "end";
13 |
14 | export class SimpleWall extends ClayElement {
15 | readonly onUpdate = new Event();
16 |
17 | attributes: IFC.IfcWall;
18 |
19 | type: SimpleWallType;
20 |
21 | body: Extrusion;
22 |
23 | height = 3;
24 |
25 | startPoint = new THREE.Vector2(0, 0);
26 |
27 | endPoint = new THREE.Vector2(1, 0);
28 |
29 | offset = 0;
30 |
31 | private _nester = new SimpleWallNester(this);
32 |
33 | get length() {
34 | return this.startPoint.distanceTo(this.endPoint);
35 | }
36 |
37 | get direction() {
38 | const vector = new THREE.Vector3();
39 | vector.subVectors(this.endPoint3D, this.startPoint3D);
40 | vector.normalize();
41 | return vector;
42 | }
43 |
44 | get normal() {
45 | const direction = this.direction;
46 | const up = new THREE.Vector3(0, 1, 0);
47 | return direction.cross(up);
48 | }
49 |
50 | get startPoint3D() {
51 | const { x, y } = this.startPoint;
52 | return new THREE.Vector3(x, this.transformation.position.y, y);
53 | }
54 |
55 | get endPoint3D() {
56 | const { x, y } = this.endPoint;
57 | return new THREE.Vector3(x, this.transformation.position.y, y);
58 | }
59 |
60 | constructor(model: Model, type: SimpleWallType) {
61 | super(model, type);
62 | this.type = type;
63 |
64 | const profile = new RectangleProfile(model);
65 | this.body = new Extrusion(model, profile);
66 | const id = this.body.attributes.expressID;
67 | this.type.geometries.set(id, this.body);
68 | this.geometries.add(id);
69 |
70 | const placement = IfcUtils.localPlacement();
71 | const shape = IfcUtils.productDefinitionShape(model, [
72 | this.body.attributes,
73 | ]);
74 |
75 | this.attributes = new IFC.IfcWall(
76 | new IFC.IfcGloballyUniqueId(uuidv4()),
77 | null,
78 | null,
79 | null,
80 | null,
81 | placement,
82 | shape,
83 | null,
84 | null,
85 | );
86 |
87 | this.model.set(this.attributes);
88 | }
89 |
90 | update(updateGeometry = false) {
91 | const profile = this.body.profile;
92 | profile.dimension.x = this.length;
93 | profile.dimension.y = this.type.width;
94 | profile.transformation.position.y = this.offset;
95 | profile.update();
96 |
97 | this.body.depth = this.height;
98 | this.body.update();
99 |
100 | const start = this.startPoint3D;
101 | const end = this.endPoint3D;
102 | const midPoint = new THREE.Vector3(
103 | (start.x + end.x) / 2,
104 | (start.y + end.y) / 2,
105 | (start.z + end.z) / 2,
106 | );
107 |
108 | const dir = this.direction;
109 | this.transformation.rotation.y = Math.atan2(-dir.z, dir.x);
110 | this.transformation.position.copy(midPoint);
111 |
112 | const shape = this.model.get(this.attributes.Representation);
113 | const reps = this.model.get(shape.Representations[0]);
114 | reps.Items = [this.body.attributes];
115 | this.model.set(reps);
116 |
117 | this.updateGeometryID();
118 |
119 | this._nester.update();
120 |
121 | super.update(updateGeometry);
122 | }
123 |
124 | async addSubtraction(element: ClayElement, nest = false) {
125 | if (this.subtractions.has(element.attributes.expressID)) {
126 | return;
127 | }
128 | await super.addSubtraction(element);
129 | if (nest) {
130 | this._nester.add(element);
131 | }
132 | this.updateGeometryID();
133 | }
134 |
135 | async removeSubtraction(element: ClayElement) {
136 | if (!this.subtractions.has(element.attributes.expressID)) {
137 | return;
138 | }
139 | await super.removeSubtraction(element);
140 | this._nester.delete(element);
141 | this.updateGeometryID();
142 | }
143 |
144 | getPlane(type: WallPlaneType = "center") {
145 | const normal = this.normal;
146 |
147 | const p1 = this.startPoint3D;
148 | const p2 = this.endPoint3D;
149 | const p3 = p1.clone();
150 | p3.y += 1;
151 |
152 | if (p1.equals(p2)) {
153 | // Zero length wall
154 | const plane = null;
155 | return { p1, p2, p3, plane };
156 | }
157 |
158 | // const offsetCorrection = normal.clone();
159 | // offsetCorrection.multiplyScalar(-this.offset);
160 |
161 | // p1.add(offsetCorrection);
162 | // p2.add(offsetCorrection);
163 | // p3.add(offsetCorrection);
164 |
165 | if (type !== "center") {
166 | const offset = normal.clone();
167 | const factor = type === "exterior" ? 1 : -1;
168 | const offsetAmount = (this.type.width / 2) * factor;
169 | offset.multiplyScalar(offsetAmount);
170 | p1.add(offset);
171 | p2.add(offset);
172 | p3.add(offset);
173 | }
174 |
175 | const plane = new THREE.Plane().setFromCoplanarPoints(p1, p2, p3);
176 | return { plane, p1, p2, p3 };
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Walls/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleWall";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Windows/SimpleWindow/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Windows/SimpleWindow/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3, IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { Model, StaticClayElementType } from "../../../core";
4 | import { Extrusion, RectangleProfile } from "../../../geometries";
5 | import { IfcUtils } from "../../../utils/ifc-utils";
6 |
7 | import { SimpleWindow } from "./src";
8 |
9 | export * from "./src";
10 |
11 | export class SimpleWindowType extends StaticClayElementType {
12 | attributes: IFC4X3.IfcFurnishingElementType;
13 |
14 | shape: IFC4X3.IfcProductDefinitionShape;
15 |
16 | frameWidth = 0.1;
17 |
18 | length = 1;
19 |
20 | width = 0.2;
21 |
22 | height = 1;
23 |
24 | private bottomFrame: Extrusion;
25 |
26 | private topFrame: Extrusion;
27 |
28 | private leftFrame: Extrusion;
29 |
30 | private rightFrame: Extrusion;
31 |
32 | constructor(model: Model) {
33 | super(model);
34 |
35 | const horizontalProfile = new RectangleProfile(model);
36 | const body1 = new Extrusion(model, horizontalProfile);
37 | const id1 = body1.attributes.expressID;
38 | const fragment = this.newFragment();
39 | this.fragments.set(id1, fragment);
40 | this.geometries.set(id1, body1);
41 | this.bottomFrame = body1;
42 |
43 | const body2 = new Extrusion(model, horizontalProfile);
44 | const id2 = body2.attributes.expressID;
45 | this.geometries.set(id2, body2);
46 | this.topFrame = body2;
47 | const fragment2 = this.newFragment();
48 | this.fragments.set(id2, fragment2);
49 |
50 | const verticalProfile = new RectangleProfile(model);
51 | const body3 = new Extrusion(model, verticalProfile);
52 | const id3 = body3.attributes.expressID;
53 | this.geometries.set(id3, body3);
54 | this.leftFrame = body3;
55 | const fragment3 = this.newFragment();
56 | this.fragments.set(id3, fragment3);
57 | body3.rotation.y = Math.PI / 2;
58 | body3.update();
59 |
60 | const body4 = new Extrusion(model, verticalProfile);
61 | const id4 = body4.attributes.expressID;
62 | this.geometries.set(id4, body4);
63 | this.rightFrame = body4;
64 | const fragment4 = this.newFragment();
65 | this.fragments.set(id4, fragment4);
66 | body4.rotation.y = Math.PI / 2;
67 | body4.update();
68 |
69 | this.shape = IfcUtils.productDefinitionShape(model, [
70 | body1.attributes,
71 | body2.attributes,
72 | body3.attributes,
73 | body4.attributes,
74 | ]);
75 |
76 | this.attributes = new IFC.IfcWindowType(
77 | new IFC.IfcGloballyUniqueId(uuidv4()),
78 | null,
79 | null,
80 | null,
81 | null,
82 | null,
83 | null,
84 | null,
85 | null,
86 | IFC.IfcWindowTypeEnum.WINDOW,
87 | IFC.IfcWindowTypePartitioningEnum.NOTDEFINED,
88 | null,
89 | null,
90 | );
91 |
92 | this.updateGeometry();
93 | this.model.set(this.attributes);
94 | }
95 |
96 | update(updateGeometry: boolean = false) {
97 | this.updateGeometry();
98 | super.update(updateGeometry);
99 | }
100 |
101 | private updateGeometry() {
102 | this.bottomFrame.profile.dimension.y = this.width;
103 | this.bottomFrame.profile.dimension.x = this.length;
104 | this.bottomFrame.profile.update();
105 |
106 | this.bottomFrame.depth = this.frameWidth;
107 | this.bottomFrame.update();
108 |
109 | this.topFrame.position.z = this.height;
110 | this.topFrame.depth = this.frameWidth;
111 | this.topFrame.update();
112 |
113 | this.rightFrame.profile.dimension.y = this.width;
114 | this.rightFrame.profile.dimension.x = this.height;
115 | this.rightFrame.profile.update();
116 |
117 | this.rightFrame.position.x = -this.length / 2;
118 | this.rightFrame.position.z = this.height / 2;
119 | this.rightFrame.depth = this.frameWidth;
120 | this.rightFrame.update();
121 |
122 | this.leftFrame.position.x = this.length / 2 - this.frameWidth;
123 | this.leftFrame.position.z = this.height / 2;
124 | this.leftFrame.depth = this.frameWidth;
125 | this.leftFrame.update();
126 | }
127 |
128 | protected createElement() {
129 | return new SimpleWindow(this.model, this);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Windows/SimpleWindow/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { Model, ClayElement } from "../../../../core";
4 | import { IfcUtils } from "../../../../utils/ifc-utils";
5 |
6 | import { SimpleWindowType } from "../index";
7 |
8 | export class SimpleWindow extends ClayElement {
9 | attributes: IFC.IfcFurnishingElement;
10 |
11 | type: SimpleWindowType;
12 |
13 | constructor(model: Model, type: SimpleWindowType) {
14 | super(model, type);
15 | this.type = type;
16 |
17 | const placement = IfcUtils.localPlacement();
18 |
19 | for (const [id] of type.geometries) {
20 | this.geometries.add(id);
21 | }
22 |
23 | this.attributes = new IFC.IfcWindow(
24 | new IFC.IfcGloballyUniqueId(uuidv4()),
25 | null,
26 | null,
27 | null,
28 | null,
29 | placement,
30 | type.shape,
31 | null,
32 | null,
33 | null,
34 | null,
35 | null,
36 | null,
37 | );
38 |
39 | this.model.set(this.attributes);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/Windows/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SimpleWindow";
2 |
--------------------------------------------------------------------------------
/packages/clay/src/elements/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Openings";
2 | export * from "./Slabs";
3 | export * from "./Walls";
4 | export * from "./Furniture";
5 | export * from "./Windows";
6 | export * from "./Plates";
7 | export * from "./Members";
8 | export * from "./CurtainWalls";
9 |
--------------------------------------------------------------------------------
/packages/clay/src/general/ElementChildren/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC, Handle } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { ClayObject, Model } from "../../core";
4 |
5 | export class ElementChildren extends ClayObject {
6 | attributes: IFC.IfcRelContainedInSpatialStructure;
7 | ids = new Set();
8 |
9 | constructor(
10 | model: Model,
11 | ownerHistory: IFC.IfcOwnerHistory,
12 | element: IFC.IfcSpatialElement,
13 | ) {
14 | super(model);
15 | this.attributes = new IFC.IfcRelContainedInSpatialStructure(
16 | new IFC.IfcGloballyUniqueId(uuidv4()),
17 | ownerHistory,
18 | null,
19 | null,
20 | [],
21 | element,
22 | );
23 |
24 | this.update();
25 | }
26 |
27 | add(itemID: number) {
28 | if (this.ids.has(itemID)) {
29 | return;
30 | }
31 | this.attributes.RelatedElements.push(new Handle(itemID));
32 | this.ids.add(itemID);
33 | this.update();
34 | }
35 |
36 | remove(itemID: number) {
37 | if (!this.ids.has(itemID)) {
38 | return;
39 | }
40 | const children = this.attributes.RelatedElements as Handle[];
41 | this.attributes.RelatedElements = children.filter(
42 | (item) => item.value !== itemID,
43 | );
44 | this.ids.delete(itemID);
45 | this.update();
46 | }
47 |
48 | update() {
49 | this.model.set(this.attributes);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/clay/src/general/Project/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import * as THREE from "three";
4 | import { ClayObject, Model } from "../../core";
5 | import { IfcUtils } from "../../utils/ifc-utils";
6 | import { SpatialChildren } from "../SpatialChildren";
7 |
8 | export class Project extends ClayObject {
9 | attributes: IFC.IfcProject;
10 |
11 | ownerHistory: IFC.IfcOwnerHistory;
12 |
13 | spatialChildren: SpatialChildren;
14 |
15 | constructor(model: Model) {
16 | super(model);
17 |
18 | const organization = new IFC.IfcOrganization(
19 | null,
20 | new IFC.IfcLabel("That Open Company"),
21 | null,
22 | null,
23 | null,
24 | );
25 |
26 | const person = new IFC.IfcPerson(
27 | null,
28 | null,
29 | null,
30 | null,
31 | null,
32 | null,
33 | null,
34 | null,
35 | );
36 |
37 | const personAndOrganization = new IFC.IfcPersonAndOrganization(
38 | person,
39 | organization,
40 | null,
41 | );
42 |
43 | const application = new IFC.IfcApplication(
44 | organization,
45 | new IFC.IfcLabel("2.4.0"),
46 | new IFC.IfcLabel("CLAY"),
47 | new IFC.IfcLabel("CLAY"),
48 | );
49 |
50 | this.ownerHistory = new IFC.IfcOwnerHistory(
51 | personAndOrganization,
52 | application,
53 | null,
54 | IFC.IfcChangeActionEnum.NOTDEFINED,
55 | null,
56 | null,
57 | null,
58 | new IFC.IfcTimeStamp(new Date().getTime()),
59 | );
60 |
61 | const context = new IFC.IfcGeometricRepresentationContext(
62 | null,
63 | null,
64 | new IFC.IfcDimensionCount(3),
65 | new IFC.IfcReal(1.0e-5),
66 | new IFC.IfcAxis2Placement3D(
67 | IfcUtils.point(new THREE.Vector3()),
68 | null,
69 | null,
70 | ),
71 | null,
72 | );
73 |
74 | const lengthUnit = new IFC.IfcSIUnit(
75 | IFC.IfcUnitEnum.LENGTHUNIT,
76 | null,
77 | IFC.IfcSIUnitName.METRE,
78 | );
79 |
80 | const areaUnit = new IFC.IfcSIUnit(
81 | IFC.IfcUnitEnum.AREAUNIT,
82 | null,
83 | IFC.IfcSIUnitName.SQUARE_METRE,
84 | );
85 |
86 | const volumeUnit = new IFC.IfcSIUnit(
87 | IFC.IfcUnitEnum.AREAUNIT,
88 | null,
89 | IFC.IfcSIUnitName.SQUARE_METRE,
90 | );
91 |
92 | const units = new IFC.IfcUnitAssignment([lengthUnit, areaUnit, volumeUnit]);
93 |
94 | this.attributes = new IFC.IfcProject(
95 | new IFC.IfcGloballyUniqueId(uuidv4()),
96 | this.ownerHistory,
97 | null,
98 | null,
99 | null,
100 | null,
101 | null,
102 | [context],
103 | units,
104 | );
105 |
106 | this.model.set(this.attributes);
107 |
108 | this.spatialChildren = new SpatialChildren(
109 | model,
110 | this.ownerHistory,
111 | this.attributes,
112 | );
113 | }
114 |
115 | update(): void {
116 | this.model.set(this.attributes);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/packages/clay/src/general/Site/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { ClayObject, Model } from "../../core";
4 | import { SpatialChildren } from "../SpatialChildren";
5 | import { ElementChildren } from "../ElementChildren";
6 | import { Project } from "../Project";
7 | import { IfcUtils } from "../../utils/ifc-utils";
8 |
9 | export class Site extends ClayObject {
10 | attributes: IFC.IfcSite;
11 |
12 | spatialChildren: SpatialChildren;
13 |
14 | children: ElementChildren;
15 |
16 | constructor(model: Model, project: Project) {
17 | super(model);
18 |
19 | this.attributes = new IFC.IfcSite(
20 | new IFC.IfcGloballyUniqueId(uuidv4()),
21 | project.ownerHistory,
22 | null,
23 | null,
24 | null,
25 | IfcUtils.localPlacement(),
26 | null,
27 | null,
28 | IFC.IfcElementCompositionEnum.ELEMENT,
29 | null,
30 | null,
31 | null,
32 | null,
33 | null,
34 | );
35 |
36 | this.model.set(this.attributes);
37 |
38 | project.spatialChildren.add(this.attributes.expressID);
39 |
40 | this.spatialChildren = new SpatialChildren(
41 | model,
42 | project.ownerHistory,
43 | this.attributes,
44 | );
45 |
46 | this.children = new ElementChildren(
47 | model,
48 | project.ownerHistory,
49 | this.attributes,
50 | );
51 | }
52 |
53 | update(): void {
54 | this.model.set(this.attributes);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/clay/src/general/SpatialChildren/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC, Handle } from "web-ifc";
2 | import { v4 as uuidv4 } from "uuid";
3 | import { ClayObject, Model } from "../../core";
4 |
5 | export class SpatialChildren extends ClayObject {
6 | attributes: IFC.IfcRelAggregates;
7 | ids = new Set();
8 |
9 | constructor(
10 | model: Model,
11 | ownerHistory: IFC.IfcOwnerHistory,
12 | element: IFC.IfcObjectDefinition,
13 | ) {
14 | super(model);
15 | this.attributes = new IFC.IfcRelAggregates(
16 | new IFC.IfcGloballyUniqueId(uuidv4()),
17 | ownerHistory,
18 | null,
19 | null,
20 | element,
21 | [],
22 | );
23 |
24 | this.update();
25 | }
26 |
27 | add(itemID: number) {
28 | if (this.ids.has(itemID)) {
29 | return;
30 | }
31 | this.attributes.RelatedObjects.push(new Handle(itemID));
32 | this.ids.add(itemID);
33 | this.update();
34 | }
35 |
36 | remove(itemID: number) {
37 | if (!this.ids.has(itemID)) {
38 | return;
39 | }
40 | const children = this.attributes.RelatedObjects as Handle[];
41 | this.attributes.RelatedObjects = children.filter(
42 | (item) => item.value !== itemID,
43 | );
44 | this.ids.delete(itemID);
45 | this.update();
46 | }
47 |
48 | update() {
49 | this.model.set(this.attributes);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/clay/src/general/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Project";
2 | export * from "./Site";
3 | export * from "./SpatialChildren";
4 | export * from "./ElementChildren";
5 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Brep/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/packages/clay/src/geometries/Brep/index.html
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Brep/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import * as THREE from "three";
3 | import { ClayGeometry, Model } from "../../core";
4 |
5 | import { IfcUtils } from "../../utils/ifc-utils";
6 |
7 | export class Brep extends ClayGeometry {
8 | attributes: IFC.IfcFacetedBrep | IFC.IfcBooleanClippingResult;
9 |
10 | core: IFC.IfcFacetedBrep;
11 |
12 | private _baseGeometry: THREE.BufferGeometry;
13 |
14 | get baseGeometry() {
15 | return this._baseGeometry;
16 | }
17 |
18 | set baseGeometry(geometry: THREE.BufferGeometry) {
19 | this.disposePreviousBrep();
20 | this._baseGeometry = geometry;
21 | this.regenerateBrep();
22 |
23 | if (this.firstClipping !== null) {
24 | const firstBool = this.clippings.get(this.firstClipping);
25 | if (!firstBool) {
26 | throw new Error("Malformed bool!");
27 | }
28 | const { bool } = firstBool;
29 | bool.FirstOperand = this.core;
30 | this.model.set(bool);
31 | }
32 |
33 | this.update();
34 | }
35 |
36 | constructor(model: Model) {
37 | super(model);
38 | this._baseGeometry = new THREE.BoxGeometry();
39 |
40 | const ifcClosedShell = new IFC.IfcClosedShell([]);
41 | this.core = new IFC.IfcFacetedBrep(ifcClosedShell);
42 | this.regenerateBrep();
43 |
44 | this.attributes = this.core;
45 | this.update();
46 | }
47 |
48 | update() {
49 | this.model.set(this.core);
50 | }
51 |
52 | private regenerateBrep() {
53 | const position = this._baseGeometry.getAttribute("position");
54 | const index = this._baseGeometry.getIndex();
55 |
56 | if (position && index) {
57 | const positions = position.array as Float32Array;
58 | const indices = index.array as Uint16Array;
59 |
60 | const ifcClosedShell = this.model.get(this.core.Outer);
61 |
62 | for (let i = 0; i < indices.length; i += 3) {
63 | const vertex1 = new THREE.Vector3().fromArray(
64 | positions,
65 | indices[i] * 3,
66 | );
67 | const vertex2 = new THREE.Vector3().fromArray(
68 | positions,
69 | indices[i + 1] * 3,
70 | );
71 | const vertex3 = new THREE.Vector3().fromArray(
72 | positions,
73 | indices[i + 2] * 3,
74 | );
75 |
76 | const triangle = [vertex1, vertex2, vertex3];
77 | const ifcFace = this.triangleToIFCFace(triangle);
78 |
79 | ifcClosedShell.CfsFaces.push(ifcFace);
80 | }
81 |
82 | this.model.set(ifcClosedShell);
83 | }
84 | }
85 |
86 | private triangleToIFCFace(triangle: THREE.Vector3[]) {
87 | const points = triangle.map((vertex) => IfcUtils.point(vertex));
88 | const polyLoop = new IFC.IfcPolyLoop(points);
89 | const faceBound = new IFC.IfcFaceBound(polyLoop, new IFC.IfcBoolean("T"));
90 | return new IFC.IfcFace([faceBound]);
91 | }
92 |
93 | private disposePreviousBrep() {
94 | const ifcClosedShell = this.model.get(this.core.Outer);
95 | for (const faceRef of ifcClosedShell.CfsFaces) {
96 | const face = this.model.get(faceRef);
97 | for (const faceBoundRef of face.Bounds) {
98 | const faceBound = this.model.get(faceBoundRef) as IFC.IfcFaceBound;
99 | const polyLoop = this.model.get(faceBound.Bound) as IFC.IfcPolyLoop;
100 | for (const pointRef of polyLoop.Polygon) {
101 | const point = this.model.get(pointRef);
102 | this.model.delete(point);
103 | }
104 | this.model.delete(polyLoop);
105 | this.model.delete(faceBound);
106 | }
107 | this.model.delete(face);
108 | }
109 | ifcClosedShell.CfsFaces = [];
110 | this.model.set(ifcClosedShell);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Extrusion/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/packages/clay/src/geometries/Extrusion/index.html
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Extrusion/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import * as THREE from "three";
3 | import { Model, ClayGeometry } from "../../core";
4 | import { Profile } from "../Profiles/Profile";
5 |
6 | import { MathUtils } from "../../utils/math-utils";
7 | import { IfcUtils } from "../../utils/ifc-utils";
8 |
9 | export class Extrusion extends ClayGeometry {
10 | attributes: IFC.IfcExtrudedAreaSolid | IFC.IfcBooleanClippingResult;
11 |
12 | core: IFC.IfcExtrudedAreaSolid;
13 |
14 | depth = 1;
15 |
16 | profile: T;
17 |
18 | position = new THREE.Vector3(0, 0, 0);
19 |
20 | rotation = new THREE.Euler();
21 |
22 | direction = new THREE.Vector3(0, 1, 0);
23 |
24 | constructor(model: Model, profile: T) {
25 | super(model);
26 | this.profile = profile;
27 |
28 | const { dirX, dirZ } = MathUtils.basisFromEuler(this.rotation);
29 |
30 | const placement = new IFC.IfcAxis2Placement3D(
31 | IfcUtils.point(this.position),
32 | IfcUtils.direction(dirZ),
33 | IfcUtils.direction(dirX),
34 | );
35 |
36 | const direction = IfcUtils.direction(this.direction);
37 |
38 | this.core = new IFC.IfcExtrudedAreaSolid(
39 | profile.attributes,
40 | placement,
41 | direction,
42 | new IFC.IfcPositiveLengthMeasure(this.depth),
43 | );
44 |
45 | this.attributes = this.core;
46 |
47 | this.update();
48 | }
49 |
50 | update() {
51 | const placement = this.model.get(this.core.Position);
52 |
53 | IfcUtils.setAxis2Placement(this.model, placement, this.transformation);
54 |
55 | const direction = this.model.get(this.core.ExtrudedDirection);
56 | direction.DirectionRatios[0].value = this.direction.z;
57 | direction.DirectionRatios[1].value = this.direction.x;
58 | direction.DirectionRatios[2].value = this.direction.y;
59 | this.model.set(direction);
60 |
61 | this.core.Depth.value = this.depth;
62 |
63 | this.model.set(this.core);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/HalfSpace/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/packages/clay/src/geometries/HalfSpace/index.html
--------------------------------------------------------------------------------
/packages/clay/src/geometries/HalfSpace/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Model, ClayGeometry } from "../../core";
4 |
5 | import { MathUtils } from "../../utils/math-utils";
6 | import { IfcUtils } from "../../utils/ifc-utils";
7 |
8 | export class HalfSpace extends ClayGeometry {
9 | attributes: IFC.IfcHalfSpaceSolid | IFC.IfcBooleanClippingResult;
10 |
11 | direction = new THREE.Vector3();
12 |
13 | core: IFC.IfcHalfSpaceSolid;
14 |
15 | constructor(model: Model) {
16 | super(model);
17 |
18 | const position = MathUtils.toIfcCoords(this.transformation.position);
19 | const rotation = MathUtils.toIfcRot(this.transformation.rotation);
20 | const { dirX, dirY } = MathUtils.basisFromEuler(rotation);
21 |
22 | const placement = new IFC.IfcAxis2Placement3D(
23 | IfcUtils.point(position),
24 | IfcUtils.direction(dirY),
25 | IfcUtils.direction(dirX),
26 | );
27 |
28 | const plane = new IFC.IfcPlane(placement);
29 |
30 | this.core = new IFC.IfcHalfSpaceSolid(plane, new IFC.IfcBoolean("F"));
31 |
32 | this.attributes = this.core;
33 |
34 | this.update();
35 | }
36 |
37 | update() {
38 | const plane = this.model.get(this.core.BaseSurface) as IFC.IfcPlane;
39 |
40 | const placement = this.model.get(plane.Position);
41 |
42 | const location = this.model.get(
43 | placement.Location,
44 | ) as IFC.IfcCartesianPoint;
45 |
46 | const position = MathUtils.toIfcCoords(this.transformation.position);
47 | const direction = MathUtils.toIfcCoords(this.direction);
48 |
49 | location.Coordinates[0].value = position.x;
50 | location.Coordinates[1].value = position.y;
51 | location.Coordinates[2].value = position.z;
52 | this.model.set(location);
53 |
54 | const zDirection = this.model.get(placement.Axis);
55 | zDirection.DirectionRatios[0].value = direction.x;
56 | zDirection.DirectionRatios[1].value = direction.y;
57 | zDirection.DirectionRatios[2].value = direction.z;
58 | this.model.set(zDirection);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/MappedItems/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/packages/clay/src/geometries/MappedItems/index.html
--------------------------------------------------------------------------------
/packages/clay/src/geometries/MappedItems/index.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import * as THREE from "three";
3 | import { ClayObject, Model, ClayGeometry } from "../../core";
4 |
5 | import { IfcUtils } from "../../utils/ifc-utils";
6 |
7 | // TODO: Are we going to use this, or is just necessary for reading?
8 | // IFCSHAPEREPRESENTATION
9 | // IFCMAPPEDITEM
10 | // IFCREPRESENTATIONMAP
11 | // IFCSHAPEREPRESENTATION
12 |
13 | export class RepresentationMap extends ClayObject {
14 | attributes: IFC.IfcRepresentationMap;
15 |
16 | geometries: T;
17 |
18 | instances = new Map<
19 | number,
20 | { item: IFC.IfcMappedItem; transform: THREE.Matrix4 }
21 | >();
22 |
23 | constructor(model: Model, geometries: T) {
24 | super(model);
25 | this.geometries = geometries;
26 |
27 | const items = geometries.map((geom) => geom.attributes);
28 | const representation = IfcUtils.shape(this.model, items);
29 |
30 | const placement = new IFC.IfcAxis2Placement3D(
31 | IfcUtils.point(new THREE.Vector3()),
32 | null,
33 | null,
34 | );
35 |
36 | this.attributes = new IFC.IfcRepresentationMap(placement, representation);
37 | this.update();
38 | }
39 |
40 | add(transformation: THREE.Matrix4) {
41 | const { position, xDir, yDir, zDir } =
42 | this.getTransformData(transformation);
43 |
44 | const operator = new IFC.IfcCartesianTransformationOperator3D(
45 | IfcUtils.direction(xDir),
46 | IfcUtils.direction(yDir),
47 | IfcUtils.point(position),
48 | new IFC.IfcReal(1),
49 | IfcUtils.direction(zDir),
50 | );
51 |
52 | const item = new IFC.IfcMappedItem(this.attributes, operator);
53 | this.model.set(item);
54 |
55 | const transform = transformation.clone();
56 | this.instances.set(item.expressID, { item, transform });
57 |
58 | return item;
59 | }
60 |
61 | set(id: number, transform: THREE.Matrix4) {
62 | const found = this.getItem(id);
63 |
64 | const { position, xDir, yDir, zDir } = this.getTransformData(transform);
65 | const operator = this.model.get(
66 | found.item.MappingTarget,
67 | ) as IFC.IfcCartesianTransformationOperator3D;
68 |
69 | const origin = this.model.get(operator.LocalOrigin);
70 | origin.Coordinates[0].value = position.x;
71 | origin.Coordinates[1].value = position.y;
72 | origin.Coordinates[2].value = position.z;
73 | this.model.set(origin);
74 |
75 | const axis1 = this.model.get(operator.Axis1);
76 | axis1.DirectionRatios[0].value = xDir.x;
77 | axis1.DirectionRatios[1].value = xDir.y;
78 | axis1.DirectionRatios[2].value = xDir.z;
79 | this.model.set(axis1);
80 |
81 | const axis2 = this.model.get(operator.Axis2);
82 | axis2.DirectionRatios[0].value = yDir.x;
83 | axis2.DirectionRatios[1].value = yDir.y;
84 | axis2.DirectionRatios[2].value = yDir.z;
85 | this.model.set(axis2);
86 |
87 | const axis3 = this.model.get(operator.Axis3);
88 | axis3.DirectionRatios[0].value = zDir.x;
89 | axis3.DirectionRatios[1].value = zDir.y;
90 | axis3.DirectionRatios[2].value = zDir.z;
91 | this.model.set(axis3);
92 | }
93 |
94 | remove(id: number) {
95 | const found = this.getItem(id);
96 |
97 | const { item } = found;
98 | const target = this.model.get(
99 | item.MappingTarget,
100 | ) as IFC.IfcCartesianTransformationOperator3D;
101 |
102 | this.model.delete(target.LocalOrigin);
103 | this.model.delete(target.Axis1);
104 | this.model.delete(target.Axis2);
105 | this.model.delete(target.Axis3);
106 | this.model.delete(target);
107 | this.model.delete(item);
108 |
109 | this.instances.delete(id);
110 | }
111 |
112 | update(): void {
113 | this.model.set(this.attributes);
114 | }
115 |
116 | private getItem(id: number) {
117 | const found = this.instances.get(id);
118 | if (!found) {
119 | throw new Error(`The instance ${id} does not exist!`);
120 | }
121 | return found;
122 | }
123 |
124 | private getTransformData(transform: THREE.Matrix4) {
125 | const position = new THREE.Vector3();
126 | const rotation = new THREE.Quaternion();
127 | const scale = new THREE.Vector3();
128 | transform.decompose(position, rotation, scale);
129 | const xDir = new THREE.Vector3(1, 0, 0);
130 | const yDir = new THREE.Vector3(0, 1, 0);
131 | const zDir = new THREE.Vector3(0, 0, 1);
132 | xDir.applyQuaternion(rotation);
133 | yDir.applyQuaternion(rotation);
134 | zDir.applyQuaternion(rotation);
135 | return { position, xDir, yDir, zDir };
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Profiles/ArbitraryClosedProfile/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { Handle, IFC4X3 as IFC } from "web-ifc";
3 | import { Profile } from "../Profile";
4 | import { Model } from "../../../core";
5 | import { IfcUtils } from "../../../utils/ifc-utils";
6 |
7 | export class ArbitraryClosedProfile extends Profile {
8 | attributes: IFC.IfcArbitraryClosedProfileDef;
9 |
10 | dimension = new THREE.Vector3(1, 1, 0);
11 |
12 | rotation = new THREE.Euler(0, 0, 0);
13 |
14 | position = new THREE.Vector3(0, 0, 0);
15 |
16 | points = new Map();
17 |
18 | constructor(model: Model) {
19 | super(model);
20 |
21 | this.attributes = new IFC.IfcArbitraryClosedProfileDef(
22 | IFC.IfcProfileTypeEnum.CURVE,
23 | null,
24 | new IFC.IfcPolyline([]),
25 | );
26 |
27 | this.model.set(this.attributes);
28 | }
29 |
30 | addPoint(x: number, y: number, z: number) {
31 | const point = new THREE.Vector3(x, y, z);
32 | const ifcPoint = IfcUtils.point(point);
33 |
34 | const polyLine = this.model.get(
35 | this.attributes.OuterCurve,
36 | ) as IFC.IfcPolyline;
37 |
38 | polyLine.Points.push(ifcPoint);
39 |
40 | this.model.set(polyLine);
41 | this.model.set(ifcPoint);
42 | this.points.set(ifcPoint.expressID, point);
43 |
44 | this.update();
45 | }
46 |
47 | removePoint(id: number) {
48 | this.points.delete(id);
49 | const polyLine = this.model.get(
50 | this.attributes.OuterCurve,
51 | ) as IFC.IfcPolyline;
52 | const points = polyLine.Points as Handle[];
53 | polyLine.Points = points.filter((point) => point.value !== id);
54 | this.model.set(polyLine);
55 | }
56 |
57 | update() {
58 | const polyLine = this.model.get(
59 | this.attributes.OuterCurve,
60 | ) as IFC.IfcPolyline;
61 |
62 | for (const pointRef of polyLine.Points) {
63 | const point = this.model.get(pointRef);
64 |
65 | const threePoint = this.points.get(point.expressID);
66 |
67 | if (!threePoint) {
68 | throw new Error("Point not found!");
69 | }
70 | const { x, y, z } = threePoint;
71 | point.Coordinates[0].value = x;
72 | point.Coordinates[1].value = y;
73 | point.Coordinates[2].value = z;
74 | this.model.set(point);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Profiles/Profile/index.ts:
--------------------------------------------------------------------------------
1 | import * as WEBIFC from "web-ifc";
2 | import { ClayObject3D } from "../../../core";
3 |
4 | export abstract class Profile extends ClayObject3D {
5 | abstract attributes: WEBIFC.IFC4X3.IfcProfileDef;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Profiles/RectangleProfile/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
15 |
20 | Tools Component
21 |
34 |
35 |
36 |
37 |
50 |
51 |
52 |
90 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Profiles/RectangleProfile/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { IFC4X3 as IFC } from "web-ifc";
3 | import { Profile } from "../Profile";
4 | import { Model } from "../../../core";
5 | import { IfcUtils } from "../../../utils/ifc-utils";
6 | import { MathUtils } from "../../../utils";
7 |
8 | export class RectangleProfile extends Profile {
9 | attributes: IFC.IfcRectangleProfileDef;
10 |
11 | dimension = new THREE.Vector3(1, 1, 0);
12 |
13 | depth = 1;
14 |
15 | constructor(model: Model) {
16 | super(model);
17 |
18 | const position = MathUtils.toIfcCoords(this.transformation.position);
19 |
20 | const placement = new IFC.IfcAxis2Placement2D(
21 | IfcUtils.point(position),
22 | IfcUtils.direction(new THREE.Vector3(1, 0, 0)),
23 | );
24 |
25 | this.attributes = new IFC.IfcRectangleProfileDef(
26 | IFC.IfcProfileTypeEnum.AREA,
27 | null,
28 | placement,
29 | new IFC.IfcPositiveLengthMeasure(this.dimension.x),
30 | new IFC.IfcPositiveLengthMeasure(this.dimension.y),
31 | );
32 |
33 | this.model.set(this.attributes);
34 | }
35 |
36 | update() {
37 | this.attributes.XDim.value = this.dimension.x;
38 | this.attributes.YDim.value = this.dimension.y;
39 |
40 | const placement = this.model.get(this.attributes.Position);
41 |
42 | IfcUtils.setAxis2Placement(this.model, placement, this.transformation);
43 |
44 | this.model.set(this.attributes);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/Profiles/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./RectangleProfile";
2 | export * from "./ArbitraryClosedProfile";
3 |
--------------------------------------------------------------------------------
/packages/clay/src/geometries/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Extrusion";
2 | export * from "./Brep";
3 | export * from "./Profiles";
4 | export * from "./HalfSpace";
5 |
--------------------------------------------------------------------------------
/packages/clay/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./primitives";
2 | export * from "./elements";
3 | export * from "./general";
4 | export * from "./utils";
5 | export * from "./elements";
6 | export * from "./geometries";
7 | export * from "./core";
8 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Extrusions/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
29 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Faces/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Lines/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
30 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Lines/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/OffsetFaces/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/OffsetFaces3D/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
30 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/OffsetFaces3D/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Planes/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
29 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Planes/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Polygons/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
29 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Polygons/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Polygons/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { Lines } from "..";
3 | import { Raycaster } from "../../utils";
4 |
5 | export class Polygons {
6 | lines = new Lines();
7 | workPlane: THREE.Mesh;
8 |
9 | list: {
10 | [id: number]: {
11 | id: number;
12 | lines: Set;
13 | };
14 | } = {};
15 |
16 | private _editMode = false;
17 | private _caster = new Raycaster();
18 | private _foundItems: THREE.Intersection[] = [];
19 |
20 | private _isClosingPolygon = false;
21 | private _newPoints: number[] = [];
22 | private _newLines: number[] = [];
23 | private _firstPointID: number | null = null;
24 |
25 | private _nextIndex = 0;
26 |
27 | get items() {
28 | return [this.lines.mesh, this.lines.vertices.mesh, this.workPlane];
29 | }
30 |
31 | get editMode() {
32 | return this._editMode;
33 | }
34 |
35 | set editMode(active: boolean) {
36 | this.workPlane.visible = active;
37 | this._caster.trackMouse = active;
38 | this._editMode = active;
39 |
40 | if (active) {
41 | window.addEventListener("mousemove", this.update);
42 | } else {
43 | window.removeEventListener("mousemove", this.update);
44 | }
45 |
46 | const wasPolygonInProcess = Boolean(this._newPoints.length);
47 | const wasDrawingCancelled = wasPolygonInProcess && !active;
48 | if (wasDrawingCancelled) {
49 | this.cancel();
50 | }
51 | }
52 |
53 | set camera(camera: THREE.Camera) {
54 | this._caster.camera = camera;
55 | }
56 |
57 | set domElement(element: HTMLCanvasElement) {
58 | this._caster.domElement = element;
59 | }
60 |
61 | constructor() {
62 | this.workPlane = this.newWorkPlane();
63 | }
64 |
65 | add() {
66 | if (!this._editMode) return;
67 | if (!this._foundItems.length) return;
68 |
69 | if (this._isClosingPolygon) {
70 | this.finishPolygon();
71 | return;
72 | }
73 |
74 | const { x, y, z } = this._foundItems[0].point;
75 |
76 | if (!this._newPoints.length) {
77 | const [firstPoint] = this.lines.addPoints([[x, y, z]]);
78 | this._firstPointID = firstPoint;
79 | this._newPoints.push(firstPoint);
80 | }
81 |
82 | const previousPoint = this._newPoints[this._newPoints.length - 1];
83 | const [newPoint] = this.lines.addPoints([[x, y, z]]);
84 | this._newPoints.push(newPoint);
85 |
86 | const [newLine] = this.lines.add([previousPoint, newPoint]);
87 | this._newLines.push(newLine);
88 |
89 | this.lines.vertices.mesh.geometry.computeBoundingSphere();
90 | }
91 |
92 | private update = () => {
93 | this._foundItems = this._caster.cast([
94 | this.workPlane,
95 | this.lines.vertices.mesh,
96 | ]);
97 |
98 | if (this.editMode) {
99 | this.updateCurrentPoint();
100 | }
101 | };
102 |
103 | private cancel() {
104 | this.lines.removePoints(this._newPoints);
105 | this._newPoints.length = 0;
106 | this._newLines.length = 0;
107 | this._firstPointID = null;
108 | }
109 |
110 | private addPolygon(lines: number[]) {
111 | const id = this._nextIndex++;
112 | this.list[id] = {
113 | id,
114 | lines: new Set(lines),
115 | };
116 | return id;
117 | }
118 |
119 | private finishPolygon() {
120 | const last = this._newPoints.pop();
121 | if (last !== undefined) {
122 | this.lines.removePoints([last]);
123 | }
124 |
125 | this._newLines.pop();
126 |
127 | const lastPoint = this._newPoints[this._newPoints.length - 1];
128 | const firstPoint = this._newPoints[0];
129 | const [newLine] = this.lines.add([lastPoint, firstPoint]);
130 | this._newLines.push(newLine);
131 |
132 | this.addPolygon(this._newLines);
133 |
134 | this._newLines.length = 0;
135 | this._newPoints.length = 0;
136 | this._isClosingPolygon = false;
137 | this._firstPointID = null;
138 | }
139 |
140 | private updateCurrentPoint() {
141 | if (!this._foundItems.length || this._firstPointID === null) {
142 | this._isClosingPolygon = false;
143 | return;
144 | }
145 |
146 | const lastIndex = this._newPoints.length - 1;
147 | const lastPoint = this._newPoints[lastIndex];
148 |
149 | let foundFirstPoint = false;
150 | let basePlane: THREE.Intersection | null = null;
151 |
152 | const index = this.lines.vertices.idMap.getIndex(this._firstPointID);
153 |
154 | for (const item of this._foundItems) {
155 | if (item.object === this.workPlane) {
156 | basePlane = item;
157 | }
158 | if (item.object === this.lines.vertices.mesh && item.index === index) {
159 | foundFirstPoint = true;
160 | }
161 | }
162 |
163 | if (foundFirstPoint) {
164 | const coords = this.lines.vertices.get(this._firstPointID);
165 | if (coords) {
166 | const [x, y, z] = coords;
167 | this.lines.setPoint(lastPoint, [x, y, z]);
168 | this._isClosingPolygon = true;
169 | return;
170 | }
171 | } else if (basePlane) {
172 | const { x, y, z } = basePlane.point;
173 | this.lines.setPoint(lastPoint, [x, y, z]);
174 | }
175 |
176 | this._isClosingPolygon = false;
177 | }
178 |
179 | private newWorkPlane() {
180 | const floorPlaneGeom = new THREE.PlaneGeometry(10, 10);
181 | const floorPlaneMaterial = new THREE.MeshBasicMaterial({
182 | transparent: true,
183 | opacity: 0.7,
184 | color: 0xc4adef,
185 | });
186 | const plane = new THREE.Mesh(floorPlaneGeom, floorPlaneMaterial);
187 | plane.rotation.x = -Math.PI / 2;
188 | plane.visible = false;
189 | return plane;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Primitive/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Primitive/index.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { Selector } from "../../utils";
3 |
4 | export abstract class Primitive {
5 | /** Physical object with a geometry and one or many materials that can be placed in the scene. */
6 | abstract mesh: {
7 | geometry: THREE.BufferGeometry;
8 | material: THREE.Material | THREE.Material[];
9 | };
10 |
11 | /**
12 | * All the selected items within this primitive.
13 | */
14 | selected = new Selector();
15 |
16 | protected _baseColor = new THREE.Color(0.5, 0.5, 0.5);
17 | protected _selectColor = new THREE.Color(1, 0, 0);
18 |
19 | /**
20 | * The list of ids of the {@link list} of items.
21 | */
22 | get ids() {
23 | const ids: number[] = [];
24 | for (const id in this.list) {
25 | ids.push(this.list[id].id);
26 | }
27 | return ids;
28 | }
29 |
30 | /**
31 | * The color of all the points.
32 | */
33 | get baseColor() {
34 | return this._baseColor;
35 | }
36 |
37 | /**
38 | * The color of all the points.
39 | */
40 | set baseColor(color: THREE.Color) {
41 | this._baseColor.copy(color);
42 | }
43 |
44 | /**
45 | * The color of all the selected points.
46 | */
47 | get selectColor() {
48 | return this._selectColor;
49 | }
50 |
51 | /**
52 | * The color of all the selected points.
53 | */
54 | set selectColor(color: THREE.Color) {
55 | this._selectColor.copy(color);
56 | }
57 |
58 | protected list: {
59 | [id: number]: {
60 | id: number;
61 | [key: string]: any;
62 | };
63 | } = {};
64 |
65 | protected get _positionBuffer() {
66 | return this.mesh.geometry.attributes.position as THREE.BufferAttribute;
67 | }
68 |
69 | protected get _colorBuffer() {
70 | return this.mesh.geometry.attributes.color as THREE.BufferAttribute;
71 | }
72 |
73 | protected get _normalBuffer() {
74 | return this.mesh.geometry.attributes.normal as THREE.BufferAttribute;
75 | }
76 |
77 | protected get _attributes() {
78 | return Object.values(
79 | this.mesh.geometry.attributes,
80 | ) as THREE.BufferAttribute[];
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Snapper/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
29 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Snapper/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Vertices/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | Components | Hello world
13 |
14 |
15 |
16 |
17 |
30 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/Vertices/index.test.ts:
--------------------------------------------------------------------------------
1 | test("adds 1 + 2 to equal 3", () => {
2 | expect(1 + 2).toBe(3);
3 | });
4 |
--------------------------------------------------------------------------------
/packages/clay/src/primitives/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Vertices";
2 | export * from "./Lines";
3 | export * from "./Faces";
4 | export * from "./OffsetFaces";
5 | export * from "./Extrusions";
6 | export * from "./OffsetFaces3D";
7 | export * from "./Primitive";
8 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/buffer-manager.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export class BufferManager {
4 | /** Buffer increment when geometry size is exceeded, multiple of 3. */
5 | bufferIncrease = 300;
6 |
7 | /**
8 | * The maximum capacity of the buffers. If exceeded by the {@link size},
9 | * the buffers will be rescaled.
10 | */
11 | capacity = 0;
12 |
13 | /** The current size of the buffers. */
14 | get size() {
15 | const firstAttribute = this.attributes[0];
16 | return firstAttribute.count * 3;
17 | }
18 |
19 | get attributes() {
20 | return Object.values(this.geometry.attributes) as THREE.BufferAttribute[];
21 | }
22 |
23 | constructor(public geometry: THREE.BufferGeometry) {}
24 |
25 | addAttribute(attribute: THREE.BufferAttribute) {
26 | this.geometry.setAttribute(attribute.name, attribute);
27 | }
28 |
29 | resetAttributes() {
30 | for (const attribute of this.attributes) {
31 | this.createAttribute(attribute.name);
32 | }
33 | this.capacity = 0;
34 | }
35 |
36 | createAttribute(name: string) {
37 | if (this.geometry.hasAttribute(name)) {
38 | this.geometry.deleteAttribute(name);
39 | }
40 | const attribute = new THREE.BufferAttribute(new Float32Array(0), 3);
41 | attribute.name = name;
42 | this.geometry.setAttribute(name, attribute);
43 | }
44 |
45 | resizeIfNeeded(increase: number) {
46 | const newSize = this.size + increase * 3;
47 | const difference = newSize - this.capacity;
48 | if (difference >= 0) {
49 | const increase = Math.max(difference, this.bufferIncrease);
50 | const oldCapacity = this.capacity;
51 | this.capacity += increase;
52 | for (const attribute of this.attributes) {
53 | this.resizeBuffers(attribute, oldCapacity);
54 | }
55 | }
56 | }
57 |
58 | private resizeBuffers(attribute: THREE.BufferAttribute, oldCapacity: number) {
59 | this.geometry.deleteAttribute(attribute.name);
60 | const array = new Float32Array(this.capacity);
61 | const newAttribute = new THREE.BufferAttribute(array, 3);
62 | newAttribute.name = attribute.name;
63 | // newAttribute.count = attribute.count;
64 | this.geometry.setAttribute(attribute.name, newAttribute);
65 | for (let i = 0; i < oldCapacity; i++) {
66 | const x = attribute.getX(i);
67 | const y = attribute.getY(i);
68 | const z = attribute.getZ(i);
69 | newAttribute.setXYZ(i, x, y, z);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/event.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple event handler by
3 | * [Jason Kleban](https://gist.github.com/JasonKleban/50cee44960c225ac1993c922563aa540).
4 | * Keep in mind that:
5 | * - If you want to remove it later, you might want to declare the callback as
6 | * an object.
7 | * - If you want to maintain the reference to `this`, you will need to declare
8 | * the callback as an arrow function.
9 | */
10 | export class Event {
11 | /**
12 | * Add a callback to this event instance.
13 | * @param handler - the callback to be added to this event.
14 | */
15 | add(handler: T extends void ? { (): void } : { (data: T): void }): void {
16 | this.handlers.push(handler);
17 | }
18 |
19 | /**
20 | * Removes a callback from this event instance.
21 | * @param handler - the callback to be removed from this event.
22 | */
23 | remove(handler: T extends void ? { (): void } : { (data: T): void }): void {
24 | this.handlers = this.handlers.filter((h) => h !== handler);
25 | }
26 |
27 | /** Triggers all the callbacks assigned to this event. */
28 | trigger = async (data?: T) => {
29 | const handlers = this.handlers.slice(0);
30 | for (const handler of handlers) {
31 | await handler(data as any);
32 | }
33 | };
34 |
35 | /** Gets rid of all the suscribed events. */
36 | reset() {
37 | this.handlers.length = 0;
38 | }
39 |
40 | private handlers: (T extends void ? { (): void } : { (data: T): void })[] =
41 | [];
42 | }
43 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/id-index-map.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * An object to keep track of entities and its position in a geometric buffer.
3 | */
4 | export class IdIndexMap {
5 | private _idGenerator = 0;
6 | private _ids: number[] = [];
7 | private _indices: (number | null)[] = [];
8 |
9 | /**
10 | * The number of items stored in this map
11 | */
12 | get size() {
13 | return this._ids.length;
14 | }
15 |
16 | /**
17 | * The list of IDs inside this map. IDs are generated as increasing natural
18 | * numbers starting from zero. The position of the ID in the array is
19 | * the index of that entity in the geometric buffer.
20 | * For instance, the ids of a map with 5 items would look like this:
21 | *
22 | * - [0, 1, 2, 3, 4]
23 | *
24 | * If the item with ID = 1 is deleted, the last item will replace the deleted
25 | * one to keep the continuity of the geometric buffer, resulting in this:
26 | *
27 | * - [0, 4, 2, 3]
28 | */
29 | get ids() {
30 | return this._ids;
31 | }
32 |
33 | /**
34 | * The list of indices of the geometric buffer. The position of the index in
35 | * the array is the ID of that entity. For instance, the ids of a map with 5
36 | * items would look like this:
37 | *
38 | * - [0, 1, 2, 3, 4]
39 | *
40 | * If the item with ID = 1 is deleted, the last item will replace the
41 | * deleted one to keep the continuity of the geometric buffer. The deleted
42 | * item will remain as null inside the array:
43 | *
44 | * - [0, null, 2, 3, 1]
45 | */
46 | get indices() {
47 | return this._indices;
48 | }
49 |
50 | /**
51 | * Adds a new item to the map, creating and assigning a new ID and a new index
52 | * to it. New items are assumed to be created at the end of the geometric
53 | * buffer.
54 | */
55 | add() {
56 | this._ids.push(this._idGenerator++);
57 | const index = this._ids.length - 1;
58 | this._indices.push(index);
59 | return index;
60 | }
61 |
62 | /**
63 | * Removes the specified item from the map and rearrange the indices to
64 | * keep the continuity of the geometric buffer.
65 | */
66 | remove(id: number) {
67 | const index = this.getIndex(id);
68 | if (index === null || index === undefined) return;
69 | const lastID = this._ids.pop();
70 | if (lastID === undefined) {
71 | throw new Error(`Error while removing item: ${id}`);
72 | }
73 | this._indices[id] = null;
74 | if (id === lastID) return;
75 | this._ids[index] = lastID;
76 | this._indices[lastID] = index;
77 | }
78 |
79 | /**
80 | * Resets this instance to the initial state.
81 | */
82 | reset() {
83 | this._idGenerator = 0;
84 | this._ids = [];
85 | this._indices = [];
86 | }
87 |
88 | /**
89 | * Gets the ID for the given index.
90 | * @param index index of the entity whose ID to find out.
91 | */
92 | getId(index: number) {
93 | return this._ids[index];
94 | }
95 |
96 | /**
97 | * Gets the index for the given ID.
98 | * @param id ID of the entity whose index to find out.
99 | */
100 | getIndex(id: number) {
101 | return this._indices[id];
102 | }
103 |
104 | /**
105 | * Gets the last index of the geometry buffer.
106 | */
107 | getLastIndex() {
108 | return this.size - 1;
109 | }
110 |
111 | /**
112 | * Gets the last ID in the geometry buffer.
113 | */
114 | getLastID() {
115 | return this._ids[this._ids.length - 1];
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/ifc-utils.ts:
--------------------------------------------------------------------------------
1 | import { IFC4X3 as IFC } from "web-ifc";
2 | import * as THREE from "three";
3 | import { Model } from "../core";
4 | import { MathUtils } from "./math-utils";
5 |
6 | export class IfcUtils {
7 | static direction(vector: THREE.Vector3) {
8 | return new IFC.IfcDirection([
9 | new IFC.IfcReal(vector.x),
10 | new IFC.IfcReal(vector.y),
11 | new IFC.IfcReal(vector.z),
12 | ]);
13 | }
14 |
15 | static point(vector: THREE.Vector3) {
16 | return new IFC.IfcCartesianPoint([
17 | new IFC.IfcLengthMeasure(vector.x),
18 | new IFC.IfcLengthMeasure(vector.y),
19 | new IFC.IfcLengthMeasure(vector.z),
20 | ]);
21 | }
22 |
23 | static localPlacement(
24 | location = new THREE.Vector3(0, 0, 0),
25 | zDirection = new THREE.Vector3(0, 0, 1),
26 | xDirection = new THREE.Vector3(1, 0, 0),
27 | ) {
28 | return new IFC.IfcLocalPlacement(
29 | null,
30 | new IFC.IfcAxis2Placement3D(
31 | IfcUtils.point(location),
32 | IfcUtils.direction(zDirection),
33 | IfcUtils.direction(xDirection),
34 | ),
35 | );
36 | }
37 |
38 | static productDefinitionShape(
39 | model: Model,
40 | items: IFC.IfcRepresentationItem[],
41 | ) {
42 | const representation = this.shape(model, items);
43 | return new IFC.IfcProductDefinitionShape(null, null, [representation]);
44 | }
45 |
46 | static shape(model: Model, items: IFC.IfcRepresentationItem[]) {
47 | return new IFC.IfcShapeRepresentation(model.context, null, null, items);
48 | }
49 |
50 | static setAxis2Placement(
51 | model: Model,
52 | placement: IFC.IfcAxis2Placement3D | IFC.IfcAxis2Placement2D,
53 | object: THREE.Object3D,
54 | ) {
55 | const location = model.get(placement.Location) as IFC.IfcCartesianPoint;
56 |
57 | const position = MathUtils.toIfcCoords(object.position);
58 | const rotation = MathUtils.toIfcRot(object.rotation) as THREE.Euler;
59 |
60 | location.Coordinates[0].value = position.x;
61 | location.Coordinates[1].value = position.y;
62 | location.Coordinates[2].value = position.z;
63 | model.set(location);
64 |
65 | const { dirX, dirZ } = MathUtils.basisFromEuler(rotation);
66 |
67 | if (placement instanceof IFC.IfcAxis2Placement3D) {
68 | const zDirection = model.get(placement.Axis);
69 | zDirection.DirectionRatios[0].value = dirZ.x;
70 | zDirection.DirectionRatios[1].value = dirZ.y;
71 | zDirection.DirectionRatios[2].value = dirZ.z;
72 | model.set(zDirection);
73 | }
74 |
75 | const xDirection = model.get(placement.RefDirection);
76 | xDirection.DirectionRatios[0].value = dirX.x;
77 | xDirection.DirectionRatios[1].value = dirX.y;
78 | xDirection.DirectionRatios[2].value = dirX.z;
79 | model.set(xDirection);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./buffer-manager";
2 | export * from "./id-index-map";
3 | export * from "./selector";
4 | export * from "./vector";
5 | export * from "./raycaster";
6 | export * from "./transform-controls";
7 | export * from "./math-utils";
8 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/math-utils.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export class MathUtils {
4 | static basisFromEuler(rotation: THREE.Euler) {
5 | const dirs = new THREE.Matrix4();
6 | dirs.makeRotationFromEuler(rotation);
7 | const dirX = new THREE.Vector3();
8 | const dirY = new THREE.Vector3();
9 | const dirZ = new THREE.Vector3();
10 | dirs.extractBasis(dirX, dirY, dirZ);
11 | return { dirX, dirY, dirZ };
12 | }
13 |
14 | static toThreeCoords(value: THREE.Vector3) {
15 | return value.clone().set(value.x, value.z, -value.y);
16 | }
17 |
18 | static toIfcCoords(value: THREE.Vector3) {
19 | return value.clone().set(value.x, -value.z, value.y);
20 | }
21 |
22 | static toThreeRot(value: THREE.Euler) {
23 | return value.clone().set(value.x, value.z, -value.y);
24 | }
25 |
26 | static toIfcRot(value: THREE.Euler) {
27 | return value.clone().set(value.x, -value.z, value.y);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/raycaster.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export class Raycaster {
4 | core: THREE.Raycaster;
5 | private _mouse = new THREE.Vector2();
6 |
7 | domElement?: HTMLCanvasElement;
8 | camera?: THREE.Camera;
9 |
10 | private _mouseEvent = new THREE.Vector2();
11 | private _trackMouse = false;
12 |
13 | get trackMouse() {
14 | return this._trackMouse;
15 | }
16 |
17 | set trackMouse(active: boolean) {
18 | this._trackMouse = active;
19 | if (active) {
20 | window.addEventListener("mousemove", this.getMousePosition);
21 | } else {
22 | window.removeEventListener("mousemove", this.getMousePosition);
23 | }
24 | }
25 |
26 | constructor() {
27 | this.core = new THREE.Raycaster();
28 | if (!this.core.params.Points) {
29 | throw new Error("Raycaster has undefined Points");
30 | }
31 |
32 | this.core.params.Points.threshold = 0.2;
33 | }
34 |
35 | cast(items: THREE.Object3D[]) {
36 | if (!this.domElement || !this.camera) {
37 | throw new Error("DOM element and camera must be initialized!");
38 | }
39 |
40 | const x = this._mouseEvent.x;
41 | const y = this._mouseEvent.y;
42 | const b = this.domElement.getBoundingClientRect();
43 | this._mouse.x = ((x - b.left) / (b.right - b.left)) * 2 - 1;
44 | this._mouse.y = -((y - b.top) / (b.bottom - b.top)) * 2 + 1;
45 |
46 | this.core.setFromCamera(this._mouse, this.camera);
47 | return this.core.intersectObjects(items);
48 | }
49 |
50 | private getMousePosition = (event: MouseEvent) => {
51 | this._mouseEvent.x = event.clientX;
52 | this._mouseEvent.y = event.clientY;
53 | };
54 | }
55 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/selector.ts:
--------------------------------------------------------------------------------
1 | export class Selector {
2 | data = new Set();
3 |
4 | /**
5 | * Select or unselects the given faces.
6 | * @param active Whether to select or unselect.
7 | * @param ids List of faces IDs to select or unselect. If not
8 | * defined, all faces will be selected or deselected.
9 | * @param allItems all the existing items.
10 | */
11 | select(active: boolean, ids: Iterable, allItems: number[]) {
12 | const all = new Set(allItems);
13 | const idsToUpdate: number[] = [];
14 | for (const id of ids) {
15 | const exists = all.has(id);
16 | if (!exists) continue;
17 |
18 | const isAlreadySelected = this.data.has(id);
19 | if (active) {
20 | if (isAlreadySelected) continue;
21 | this.data.add(id);
22 | idsToUpdate.push(id);
23 | } else {
24 | if (!isAlreadySelected) continue;
25 | this.data.delete(id);
26 | idsToUpdate.push(id);
27 | }
28 | }
29 | return idsToUpdate;
30 | }
31 |
32 | getUnselected(ids: number[]) {
33 | const notSelectedIDs: number[] = [];
34 | for (const id of ids) {
35 | if (!this.data.has(id)) {
36 | notSelectedIDs.push(id);
37 | }
38 | }
39 | return notSelectedIDs;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/transform-controls.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
3 | import { Event } from "./event";
4 |
5 | export class Control {
6 | core: TransformControls;
7 | helper: THREE.Object3D;
8 | transformed = new Event();
9 | controlsActivated = new Event();
10 |
11 | get items() {
12 | return [this.helper, this.core];
13 | }
14 |
15 | constructor(camera: THREE.Camera, element: HTMLCanvasElement) {
16 | this.core = new TransformControls(camera, element);
17 |
18 | this.helper = new THREE.Object3D();
19 | let transform = new THREE.Matrix4();
20 | this.core.attach(this.helper);
21 |
22 | this.core.addEventListener("dragging-changed", () => {
23 | this.controlsActivated.trigger();
24 | });
25 |
26 | this.core.addEventListener("change", () => {
27 | this.helper.updateMatrix();
28 | const temp = this.helper.matrix.clone();
29 | temp.multiply(transform.invert());
30 | this.transformed.trigger(temp);
31 | transform = this.helper.matrix.clone();
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/clay/src/utils/vector.ts:
--------------------------------------------------------------------------------
1 | export class Vector {
2 | static get up() {
3 | return [0, 1, 0];
4 | }
5 |
6 | static round(vector: number[], precission = 1000) {
7 | return [
8 | Math.round(vector[0] * precission) / precission,
9 | Math.round(vector[1] * precission) / precission,
10 | Math.round(vector[2] * precission) / precission,
11 | ];
12 | }
13 |
14 | static getNormal(points: number[][]) {
15 | const a = Vector.subtract(points[0], points[1]);
16 | const b = Vector.subtract(points[1], points[2]);
17 |
18 | const [x, y, z] = this.multiply(a, b);
19 |
20 | const magnitude = Math.sqrt(x * x + y * y + z * z);
21 |
22 | return [x / magnitude, y / magnitude, z / magnitude];
23 | }
24 |
25 | static dot(v1: number[], v2: number[]) {
26 | return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
27 | }
28 |
29 | static multiply(v1: number[], v2: number[]) {
30 | const x = v1[1] * v2[2] - v1[2] * v2[1];
31 | const y = v1[2] * v2[0] - v1[0] * v2[2];
32 | const z = v1[0] * v2[1] - v1[1] * v2[0];
33 | return [x, y, z];
34 | }
35 |
36 | static normalize(vector: number[]) {
37 | const [x, y, z] = vector;
38 | const magnitude = Vector.magnitude(vector);
39 | return [x / magnitude, y / magnitude, z / magnitude];
40 | }
41 |
42 | static magnitude(vector: number[]) {
43 | const [x, y, z] = vector;
44 | return Math.sqrt(x * x + y * y + z * z);
45 | }
46 |
47 | static squaredMagnitude(vector: number[]) {
48 | const [x, y, z] = vector;
49 | return x * x + y * y + z * z;
50 | }
51 |
52 | static add(...vectors: number[][]) {
53 | const result = [0, 0, 0];
54 | for (const vector of vectors) {
55 | result[0] += vector[0];
56 | result[1] += vector[1];
57 | result[2] += vector[2];
58 | }
59 | return result as [number, number, number];
60 | }
61 |
62 | static subtract(v1: number[], v2: number[]) {
63 | const [x1, y1, z1] = v1;
64 | const [x2, y2, z2] = v2;
65 | return [x2 - x1, y2 - y1, z2 - z1];
66 | }
67 |
68 | static multiplyScalar(vector: number[], scalar: number) {
69 | return [vector[0] * scalar, vector[1] * scalar, vector[2] * scalar];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/packages/clay/tsconfig-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["./src"],
4 | "exclude": ["./src/**/example.ts"]
5 | }
--------------------------------------------------------------------------------
/packages/clay/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "Bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | "allowJs": false,
23 | "preserveSymlinks": true,
24 | "forceConsistentCasingInFileNames": true,
25 | "esModuleInterop": true
26 | },
27 | "references": [{ "path": "./tsconfig.node.json" }],
28 | "typedocOptions": {
29 | "entryPoints": ["./src/index.ts"],
30 | "readme": "README.md",
31 | "includeVersion": true,
32 | "excludeExternals": true,
33 | "excludeNotDocumented": true,
34 | "excludePrivate": true,
35 | "excludeProtected": true,
36 | "excludeReferences": true
37 | },
38 | }
--------------------------------------------------------------------------------
/packages/clay/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["./vite.config.ts", "./package.json"]
10 | }
--------------------------------------------------------------------------------
/packages/clay/vite.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import dts from "vite-plugin-dts";
3 | import { defineConfig } from "vite";
4 | import * as path from "path";
5 | import * as fs from "fs";
6 | import * as packageJson from "./package.json";
7 |
8 | const generateTSNamespace = (dts: Map) => {
9 | if (!fs.existsSync("./dist")) return;
10 | console.log("Generating namespace!");
11 | let types = "";
12 | dts.forEach((declaration) => {
13 | const cleanedType = declaration
14 | .replace(/export\s+\*?\s+from\s+"[^"]+";/g, "")
15 | .replace(/^\s*[\r\n]/gm, "")
16 | .replace(/`/g, "'");
17 | types += cleanedType;
18 | });
19 | fs.writeFileSync(
20 | "./dist/namespace.d.ts",
21 | `declare namespace OBC {\n${types}\n}`,
22 | );
23 | };
24 |
25 | export default defineConfig({
26 | build: {
27 | outDir: "./dist",
28 | lib: {
29 | entry: path.resolve(__dirname, "./src/index.ts"),
30 | formats: ["es", "cjs"],
31 | fileName: (format) => {
32 | const map = {
33 | cjs: "cjs",
34 | es: "mjs",
35 | };
36 | return `index.${map[format]}`;
37 | },
38 | },
39 | rollupOptions: {
40 | external: Object.keys(packageJson.peerDependencies),
41 | output: {
42 | globals: {
43 | three: "THREE",
44 | "@thatopen/fragments": "FRAGS",
45 | "web-ifc": "WEB-IFC",
46 | },
47 | },
48 | },
49 | },
50 | plugins: [
51 | dts({
52 | include: ["./src"],
53 | exclude: ["./src/**/example.ts", "./src/**/*.test.ts"],
54 | afterBuild: generateTSNamespace,
55 | }),
56 | ],
57 | });
58 |
--------------------------------------------------------------------------------
/resources/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/.DS_Store
--------------------------------------------------------------------------------
/resources/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/cover.png
--------------------------------------------------------------------------------
/resources/door.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/door.glb
--------------------------------------------------------------------------------
/resources/doors.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/doors.glb
--------------------------------------------------------------------------------
/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/favicon.ico
--------------------------------------------------------------------------------
/resources/simple-window.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/simple-window.blend
--------------------------------------------------------------------------------
/resources/simple-window.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatOpen/engine_clay/6f14cbe14c8720ddb0678d312c8628b847238fe3/resources/simple-window.glb
--------------------------------------------------------------------------------
/tsconfig.jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "ES2018",
8 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
9 | "module": "ESNext",
10 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
11 | // "lib": [], /* Specify library files to be included in the compilation. */
12 | "allowJs": true,
13 | /* Allow javascript files to be compiled. */
14 | // "checkJs": true, /* Report errors in .js files. */
15 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
16 | "declaration": true,
17 | /* Generates corresponding '.d.ts' file. */
18 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
19 | "sourceMap": true,
20 | /* Generates corresponding '.map' file. */
21 | // "outFile": "./", /* Concatenate and emit output to single file. */
22 | "outDir": "./dist",
23 | /* Redirect output structure to the directory. */
24 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
25 | // "composite": true, /* Enable project compilation */
26 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
27 | // "removeComments": true, /* Do not emit comments to output. */
28 | // "noEmit": true, /* Do not emit outputs. */
29 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
30 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
31 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
32 |
33 | /* Strict Type-Checking Options */
34 | "strict": true,
35 | /* Enable all strict type-checking options. */
36 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
37 | // "strictNullChecks": true, /* Enable strict null checks. */
38 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
39 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
40 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
41 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
42 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
43 |
44 | /* Additional Checks */
45 | // "noUnusedLocals": true, /* Report errors on unused locals. */
46 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
47 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
48 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
49 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
50 |
51 | /* Module Resolution Options */
52 | "moduleResolution": "node",
53 | /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
54 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
55 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
56 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
57 | // "typeRoots": [], /* List of folders to include type definitions from. */
58 | // "types": [], /* Type declaration files to be included in compilation. */
59 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
60 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
61 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
62 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
63 |
64 | /* Source Map Options */
65 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
67 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
68 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
69 |
70 | /* Experimental Options */
71 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
72 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
73 |
74 | /* Advanced Options */
75 | "skipLibCheck": true,
76 | /* Skip type checking of declaration files. */
77 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
78 | },
79 | "include": ["packages/**/src/**/*"],
80 | "exclude": ["node_modules"]
81 | }
82 |
--------------------------------------------------------------------------------
/vite.config-examples.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { defineConfig } from "vite";
3 | import * as path from "path";
4 | import * as fs from "fs";
5 | import { globSync } from "glob";
6 |
7 | const restructureExamples = () => {
8 | return {
9 | name: "examples-refactor",
10 | async writeBundle() {
11 | const outDir = "examples/packages";
12 | const files = globSync(`${outDir}/**/example.html`);
13 |
14 | for (const file of files) {
15 | const directory = path.dirname(file);
16 | const exampleName = path.basename(directory);
17 | const rootFolder = directory.split(path.sep)[0];
18 | const targetDirectory = path.join(rootFolder, exampleName);
19 | if (!fs.existsSync(targetDirectory)) fs.mkdirSync(targetDirectory);
20 |
21 | const buffer = fs.readFileSync(file);
22 | const newBuffer = buffer
23 | .toString()
24 | .replace(/(\.\.\/)+assets/g, "../assets")
25 | .replace(/(\.\.\/)+resources/g, "../../resources");
26 | fs.writeFileSync(path.join(targetDirectory, "index.html"), newBuffer);
27 | }
28 |
29 | if (fs.existsSync(outDir)) fs.rmSync(outDir, { recursive: true });
30 | },
31 | };
32 | };
33 |
34 | const entries = globSync("packages/**/src/**/example.html").map((file) => {
35 | const directory = path.dirname(file);
36 | const exampleName = path.basename(directory);
37 | const fixedName = exampleName[0].toLowerCase() + exampleName.slice(1);
38 | const entry = [fixedName, path.resolve(file)];
39 | return entry;
40 | });
41 |
42 | const input = Object.fromEntries(entries);
43 |
44 | export default defineConfig({
45 | base: "./",
46 | esbuild: {
47 | supported: {
48 | "top-level-await": true,
49 | },
50 | },
51 | build: {
52 | outDir: "./examples",
53 | rollupOptions: {
54 | input,
55 | output: {
56 | entryFileNames: "assets/[name].js",
57 | },
58 | },
59 | },
60 | plugins: [restructureExamples()],
61 | });
62 |
--------------------------------------------------------------------------------