├── .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 | ![cover](resources/cover.png) 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 | --------------------------------------------------------------------------------