├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── angular.json ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── projects ├── angular-stl-model-viewer │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── angular-stl-model-viewer.component.spec.ts │ │ │ ├── angular-stl-model-viewer.component.ts │ │ │ └── angular-stl-model-viewer.module.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json └── examples │ ├── browserslist │ ├── karma.conf.js │ ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ └── app.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── peter.stl │ │ └── strap.stl │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts │ ├── tsconfig.app.json │ └── tsconfig.spec.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | 9 | - name: Use Node.js 10 | uses: actions/setup-node@v3 11 | with: 12 | node-version-file: 'package.json' 13 | cache: 'npm' 14 | 15 | - name: install libgbm1 16 | run: sudo apt-get install -y libgbm1 17 | 18 | - name: Intall deps 19 | run: | 20 | npm i 21 | 22 | - name: Lint 23 | run: npm run lint 24 | 25 | - name: Test 26 | run: npm run test -- --watch=false 27 | 28 | - name: Build 29 | run: npm run build:prod 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 20 * * 6' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['javascript'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v3 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v2 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v2 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v2 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | .angular/ 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "angular.ng-template", 5 | "EditorConfig.EditorConfig" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 3 | "editor.formatOnSave": true, 4 | "eslint.format.enable": true, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll": "explicit", 7 | "source.organizeImports": "explicit", 8 | "source.fixAll.eslint": "explicit" 9 | }, 10 | "yaml.schemas": { 11 | "https://json.schemastore.org/github-workflow.json": "**/.github/workflows/*.yml" 12 | }, 13 | "angular.forceStrictTemplates": true, 14 | "prettier.enable": false 15 | } 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dev@tevim.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) since 2020 Marcus Kirsch & Bengt Weiße GbR - codaline 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-stl-model-viewer 2 | == 3 | 4 | This is an angular component to render stl-models with THREE.js. 5 | 6 | Installation 7 | == 8 | - `yarn add angular-stl-model-viewer` 9 | - `npm install angular-stl-model-viewer` 10 | 11 | Usage 12 | == 13 | - import `StlModelViewerModule` to your app module 14 | - use stl-model-viewer component in your html `` 15 | - alternatively use `[stlModelFiles]="['stlFileContent']"` 16 | 17 | 18 | Example 19 | = 20 | 21 | The working basic example can be found in our [live demo][live-demo]. 22 | 23 | Configuration 24 | == 25 | ## Input Properties 26 | | Attr | Type | Default | Details | 27 | | ------------ | ---------------------- | -------------------------------------------------------------------------------- |--------------------------------------------------- | 28 | | stlModels | string[] | empty array | List of stl model paths 29 | | stlModelFiles | string[] | empty array | List of stl model files/content 30 | | hasControls | boolean | true | If true, the user can interact with the stl-models | 31 | | camera | THREE.Camera | THREE.PerspectiveCamera( 35, WindowInnerWidth / WindowInnerHeight, 1, 15 ) | The projection mode used for rendering the scene | 32 | | cameraTarget | THREE.Vector3 | THREE.Vector3( 0, 0, 0 ) | The orientation point for the camera | 33 | | light | THREE.Light | THREE.PointLight( 0xffffff ) | Illuminates the scene | 34 | | material | THREE.MeshMaterialType | THREE.MeshPhongMaterial({ color: 0xc4c4c4, shininess: 100, specular: 0x111111 }) | Casts more precisely the possible materials assignable to a [ [Mesh]] object | 35 | | scene | THREE.Scene | THREE.Scene() | Scenes allow you to set up what and where is to be rendered by three.js. This is where you place objects, lights and cameras | 36 | | renderer | THREE.WebGLRenderer | THREE.WebGLRenderer({ antialias: true }) | Displays your beautifully crafted scenes using WebGL | 37 | | controls | THREE.OrbitControls | THREE.OrbitControls | Allow the camera to orbit around a target | 38 | | meshOptions | MeshOptions[] | [] | customize mesh options per stl-model 39 | | centered | boolean | true | Flag if models should be centered 40 | 41 | 42 | ## Output Events 43 | | Attr | Details | 44 | | ---------- | ---------------------------------------- | 45 | | rendered | Emitted when the stl-model is rendered. | 46 | 47 | 48 | ## MeshOptionsType 49 | 50 | | Attr | Type | Default | Details | 51 | | ------------- | -------------------- | ------------------------------- | --------------------------------------- | 52 | | castShadow | boolean | true | Gets rendered into shadow map | 53 | | position | THREE.Vector3 | THREE.Vector3( 0, 0, 0 ) | Object's local position | 54 | | receiveShadow | boolean | true | Material gets baked in shadow receiving | 55 | | scale | THREE.Vector3 | THREE.Vector3( 0.03, 0.03, 0.03 ) | Object's local scale | 56 | | up | THREE.Vector3 | - | Up direction | 57 | | userData | {[key: string]: any} | - | An object that can be used to store custom data about the Object3d. It should not hold references to functions as these will not be cloned | 58 | | visible | boolean | - | Object gets rendered if true | 59 | 60 | Contributing 61 | === 62 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using `npm run lint` and `npm run test`. 63 | 64 | 65 | [live-demo]: https://codaline-io.github.io/angular-stl-model-viewer 66 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-stl-model-viewer": { 7 | "projectType": "library", 8 | "root": "projects/angular-stl-model-viewer", 9 | "sourceRoot": "projects/angular-stl-model-viewer/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "tsConfig": "projects/angular-stl-model-viewer/tsconfig.lib.json", 16 | "project": "projects/angular-stl-model-viewer/ng-package.json" 17 | }, 18 | "configurations": { 19 | "production": { 20 | "tsConfig": "projects/angular-stl-model-viewer/tsconfig.lib.prod.json" 21 | } 22 | } 23 | }, 24 | "test": { 25 | "builder": "@angular-devkit/build-angular:karma", 26 | "options": { 27 | "main": "projects/angular-stl-model-viewer/src/test.ts", 28 | "tsConfig": "projects/angular-stl-model-viewer/tsconfig.spec.json", 29 | "karmaConfig": "projects/angular-stl-model-viewer/karma.conf.js" 30 | } 31 | } 32 | } 33 | }, 34 | "examples": { 35 | "projectType": "application", 36 | "schematics": {}, 37 | "root": "projects/examples", 38 | "sourceRoot": "projects/examples/src", 39 | "prefix": "app", 40 | "architect": { 41 | "build": { 42 | "builder": "@angular-devkit/build-angular:application", 43 | "options": { 44 | "outputPath": "dist/examples", 45 | "index": "projects/examples/src/index.html", 46 | "browser": "projects/examples/src/main.ts", 47 | "polyfills": ["projects/examples/src/polyfills.ts"], 48 | "tsConfig": "projects/examples/tsconfig.app.json", 49 | "aot": true, 50 | "baseHref" : "angular-stl-model-viewer", 51 | "assets": [ 52 | "projects/examples/src/favicon.ico", 53 | "projects/examples/src/assets" 54 | ], 55 | "styles": [ 56 | "projects/examples/src/styles.css" 57 | ], 58 | "scripts": [] 59 | }, 60 | "configurations": { 61 | "production": { 62 | "fileReplacements": [ 63 | { 64 | "replace": "projects/examples/src/environments/environment.ts", 65 | "with": "projects/examples/src/environments/environment.prod.ts" 66 | } 67 | ], 68 | "optimization": true, 69 | "outputHashing": "all", 70 | "sourceMap": false, 71 | "namedChunks": false, 72 | "extractLicenses": true, 73 | "budgets": [ 74 | { 75 | "type": "initial", 76 | "maximumWarning": "2mb", 77 | "maximumError": "5mb" 78 | }, 79 | { 80 | "type": "anyComponentStyle", 81 | "maximumWarning": "6kb", 82 | "maximumError": "10kb" 83 | } 84 | ] 85 | } 86 | } 87 | }, 88 | "serve": { 89 | "builder": "@angular-devkit/build-angular:dev-server", 90 | "options": { 91 | "buildTarget": "examples:build" 92 | }, 93 | "configurations": { 94 | "production": { 95 | "buildTarget": "examples:build:production" 96 | } 97 | } 98 | }, 99 | "extract-i18n": { 100 | "builder": "@angular-devkit/build-angular:extract-i18n", 101 | "options": { 102 | "buildTarget": "examples:build" 103 | } 104 | }, 105 | "test": { 106 | "builder": "@angular-devkit/build-angular:karma", 107 | "options": { 108 | "main": "projects/examples/src/test.ts", 109 | "polyfills": "projects/examples/src/polyfills.ts", 110 | "tsConfig": "projects/examples/tsconfig.spec.json", 111 | "karmaConfig": "projects/examples/karma.conf.js", 112 | "assets": [ 113 | "projects/examples/src/favicon.ico", 114 | "projects/examples/src/assets" 115 | ], 116 | "styles": [ 117 | "projects/examples/src/styles.css" 118 | ], 119 | "scripts": [] 120 | } 121 | } 122 | } 123 | } 124 | }, 125 | "cli": { 126 | "analytics": false 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { dirname } from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | import js from '@eslint/js' 6 | import json from '@eslint/json' 7 | import stylistic from '@stylistic/eslint-plugin' 8 | import angular from 'angular-eslint' 9 | import globals from 'globals' 10 | import tseslint from 'typescript-eslint' 11 | 12 | const __filename = fileURLToPath(import.meta.url) 13 | const __dirname = dirname(__filename) 14 | 15 | export default tseslint.config( 16 | { 17 | name: 'ignores', 18 | ignores: [ 19 | '/**/node_modules/*', 20 | 'node_modules/', 21 | 'dist/', 22 | '.nx/', 23 | '.angular/', 24 | 'coverage/' 25 | ] 26 | }, 27 | 28 | { 29 | name: 'lib tsc options', 30 | files: ['projects/angular-stl-model-viewer/**/*.{ts,mts,tsx}'], 31 | languageOptions: { 32 | parser: tseslint.parser, 33 | parserOptions: { 34 | project: ['tsconfig.lib.json', 'tsconfig.spec.json'], 35 | tsconfigRootDir: 'projects/angular-stl-model-viewer' 36 | } 37 | } 38 | }, 39 | { 40 | name: 'examples tsc options', 41 | files: ['projects/examples/**/*.{ts,mts,tsx}'], 42 | languageOptions: { 43 | parser: tseslint.parser, 44 | parserOptions: { 45 | project: ['tsconfig.app.json', 'tsconfig.spec.json'], 46 | tsconfigRootDir: 'projects/examples' 47 | } 48 | } 49 | }, 50 | { 51 | name: 'angular ts recommended rules', 52 | files: ['projects/angular-stl-model-viewer/**/*.{ts,mts,tsx}', 'projects/examples/**/*.{ts,mts,tsx}'], 53 | plugins: { 54 | '@stylistic': stylistic 55 | }, 56 | extends: [...tseslint.configs.recommended, ...tseslint.configs.stylistic, ...angular.configs.tsRecommended, stylistic.configs['recommended-flat']], 57 | rules: { 58 | '@angular-eslint/no-output-on-prefix': 'off', 59 | '@angular-eslint/component-class-suffix': [ 60 | 'off', 61 | { 62 | type: 'element', 63 | prefix: 'lib', 64 | style: 'kebab-case' 65 | } 66 | ], 67 | '@angular-eslint/component-selector': [ 68 | 'off', 69 | { 70 | type: 'element', 71 | prefix: 'lib', 72 | style: 'kebab-case' 73 | } 74 | ], 75 | '@angular-eslint/directive-selector': [ 76 | 'off', 77 | { 78 | type: 'attribute', 79 | prefix: 'lib', 80 | style: 'camelCase' 81 | } 82 | ], 83 | 84 | '@stylistic/no-multiple-empty-lines': [ 85 | 'error', 86 | { 87 | max: 1, 88 | maxBOF: 0, 89 | maxEOF: 1 90 | } 91 | ], 92 | '@stylistic/semi': ['error', 'never'], 93 | '@stylistic/member-delimiter-style': [ 94 | 'error', 95 | { 96 | multiline: { 97 | delimiter: 'none', 98 | requireLast: true 99 | }, 100 | singleline: { 101 | delimiter: 'semi', 102 | requireLast: false 103 | }, 104 | multilineDetection: 'brackets' 105 | } 106 | ], 107 | '@stylistic/comma-dangle': ['error', 'never'], 108 | '@stylistic/object-property-newline': ['error'], 109 | '@stylistic/object-curly-newline': ['error'], 110 | '@stylistic/object-curly-spacing': ['error', 'always'], 111 | 112 | '@typescript-eslint/no-non-null-assertion': 'off', 113 | '@typescript-eslint/no-explicit-any': ['warn'], 114 | '@typescript-eslint/no-empty-function': ['warn'] 115 | } 116 | }, 117 | { 118 | name: 'Angular template recommended rules', 119 | files: ['projects/angular-stl-model-viewer/**/*.html', 'projects/examples/**/*.html'], 120 | extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility], 121 | rules: { 122 | '@angular-eslint/template/no-negated-async': 'off', 123 | '@angular-eslint/template/click-events-have-key-events': ['warn'], 124 | '@angular-eslint/template/interactive-supports-focus': ['warn'] 125 | } 126 | }, 127 | 128 | // root files 129 | { 130 | name: 'Root files tsc options', 131 | files: ['*.{ts,tsx,mts}'], 132 | languageOptions: { 133 | parser: tseslint.parser, 134 | parserOptions: { 135 | project: ['tsconfig.json'], 136 | tsconfigRootDir: __dirname 137 | } 138 | } 139 | }, 140 | { 141 | name: 'Scripts stylistic ts recommended rules', 142 | files: ['*.{ts,tsx,mts}'], 143 | plugins: { 144 | '@stylistic': stylistic 145 | }, 146 | extends: [...tseslint.configs.recommended, ...tseslint.configs.stylistic, stylistic.configs['recommended-flat']], 147 | rules: { 148 | '@stylistic/no-multiple-empty-lines': [ 149 | 'error', 150 | { 151 | max: 1, 152 | maxBOF: 0, 153 | maxEOF: 1 154 | } 155 | ], 156 | '@stylistic/semi': ['error', 'never'], 157 | '@stylistic/member-delimiter-style': [ 158 | 'error', 159 | { 160 | multiline: { 161 | delimiter: 'none', 162 | requireLast: true 163 | }, 164 | singleline: { 165 | delimiter: 'semi', 166 | requireLast: false 167 | }, 168 | multilineDetection: 'brackets' 169 | } 170 | ], 171 | '@stylistic/comma-dangle': ['error', 'never'], 172 | '@stylistic/object-property-newline': ['error'], 173 | '@stylistic/object-curly-newline': ['error'], 174 | '@stylistic/object-curly-spacing': ['error', 'always'], 175 | 176 | '@typescript-eslint/no-non-null-assertion': 'off', 177 | '@typescript-eslint/no-explicit-any': ['warn'], 178 | '@typescript-eslint/no-empty-function': ['warn'] 179 | } 180 | }, 181 | 182 | { 183 | name: 'JS', 184 | files: ['**/*.js', '**/*.mjs', '**/*.cjs'], 185 | plugins: { 186 | js, 187 | '@stylistic': stylistic 188 | }, 189 | extends: [js.configs.recommended, stylistic.configs['recommended-flat']], 190 | languageOptions: { 191 | globals: { 192 | ...globals.node, 193 | document: false 194 | } 195 | }, 196 | rules: { 197 | '@stylistic/no-multiple-empty-lines': [ 198 | 'error', 199 | { 200 | max: 1, 201 | maxBOF: 0, 202 | maxEOF: 1 203 | } 204 | ], 205 | '@stylistic/semi': ['error', 'never'], 206 | '@stylistic/member-delimiter-style': [ 207 | 'error', 208 | { 209 | multiline: { 210 | delimiter: 'none', 211 | requireLast: true 212 | }, 213 | singleline: { 214 | delimiter: 'semi', 215 | requireLast: false 216 | }, 217 | multilineDetection: 'brackets' 218 | } 219 | ], 220 | '@stylistic/comma-dangle': ['error', 'never'] 221 | } 222 | }, 223 | 224 | // lint JSON files 225 | { 226 | files: ['**/*.json'], 227 | plugins: { 228 | json, 229 | '@stylistic': stylistic 230 | }, 231 | ignores: ['package-lock.json'], 232 | language: 'json/json', 233 | extends: [json.configs.recommended] 234 | }, 235 | 236 | // lint JSONC files 237 | { 238 | files: ['**/*.jsonc'], 239 | plugins: { 240 | json, 241 | '@stylistic': stylistic 242 | }, 243 | language: 'json/jsonc', 244 | extends: [json.configs.recommended] 245 | }, 246 | 247 | // lint JSON5 files 248 | { 249 | files: ['**/*.json5'], 250 | plugins: { 251 | json, 252 | '@stylistic': stylistic 253 | }, 254 | language: 'json/json5', 255 | extends: [json.configs.recommended] 256 | } 257 | ) 258 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-stl-model-viewer", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "bugs": "https://github.com/codaline-io/angular-stl-model-viewer/issues", 6 | "homepage": "https://github.com/codaline-io/angular-stl-model-viewer", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/codaline-io/angular-stl-model-viewer" 10 | }, 11 | "engines": { 12 | "node": "^18.19.1 || ^20.11.1 || >=22.0.0" 13 | }, 14 | "scripts": { 15 | "ng": "ng", 16 | "start": "ng serve", 17 | "build": "ng build", 18 | "build:prod": "ng build --project angular-stl-model-viewer --configuration production && cp ./README.md ./dist/angular-stl-model-viewer && cp ./LICENSE ./dist/angular-stl-model-viewer", 19 | "build:deploy": "ng build --configuration production --project examples && gh-pages -d ./dist/examples/browser", 20 | "test": "ng test --project angular-stl-model-viewer", 21 | "lint": "eslint ." 22 | }, 23 | "dependencies": { 24 | "@angular/animations": "^20.0.0", 25 | "@angular/common": "^20.0.0", 26 | "@angular/compiler": "^20.0.0", 27 | "@angular/core": "^20.0.0", 28 | "@angular/forms": "^20.0.0", 29 | "@angular/platform-browser": "^20.0.0", 30 | "@angular/platform-browser-dynamic": "^20.0.0", 31 | "@angular/router": "^20.0.0", 32 | "rxjs": "^7.8.1", 33 | "three": "^0.170.0", 34 | "tslib": "^2.7.0", 35 | "zone.js": "^0.15.0" 36 | }, 37 | "devDependencies": { 38 | "@angular-devkit/architect": "^0.2000.0", 39 | "@angular-devkit/build-angular": "^20.0.0", 40 | "@angular-devkit/core": "^20.0.0", 41 | "@angular-devkit/schematics": "^20.0.0", 42 | "@angular/cli": "^20.0.0", 43 | "@angular/compiler-cli": "^20.0.0", 44 | "@angular/localize": "^20.0.0", 45 | "@eslint/js": "^9.28.0", 46 | "@eslint/json": "^0.12.0", 47 | "@stylistic/eslint-plugin": "^4.4.1", 48 | "@types/jasmine": "^5.1.8", 49 | "@types/jasminewd2": "^2.0.13", 50 | "@types/node": "^22.15.29", 51 | "angular-eslint": "^19.7.1", 52 | "eslint": "^9.28.0", 53 | "gh-pages": "^6.3.0", 54 | "jasmine-core": "^5.7.1", 55 | "jasmine-spec-reporter": "^7.0.0", 56 | "karma": "^6.4.4", 57 | "karma-coverage": "^2.2.1", 58 | "karma-firefox-launcher": "^2.1.3", 59 | "karma-jasmine": "^5.1.0", 60 | "karma-jasmine-html-reporter": "^2.1.0", 61 | "ng-packagr": "^20.0.0", 62 | "puppeteer": "^24.10.0", 63 | "typescript": "~5.8.3", 64 | "typescript-eslint": "^8.33.1" 65 | }, 66 | "overrides": { 67 | "angular-eslint": { 68 | "@angular/core": "$@angular/core" 69 | } 70 | }, 71 | "volta": { 72 | "node": "22.15.1" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/README.md: -------------------------------------------------------------------------------- 1 | # AngularStlModelViewer 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project angular-stl-model-viewer` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project angular-stl-model-viewer`. 8 | > Note: Don't forget to add `--project angular-stl-model-viewer` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build angular-stl-model-viewer` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build angular-stl-model-viewer`, go to the dist folder `cd dist/angular-stl-model-viewer` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test angular-stl-model-viewer` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 25 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | process.env.CHROME_BIN = require('puppeteer').executablePath() 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | basePath: '', 8 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 9 | plugins: [ 10 | require('karma-jasmine'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('karma-firefox-launcher'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | preprocessors: { 20 | 'src/**/*.ts': ['coverage'] 21 | }, 22 | coverageReporter: { 23 | dir: require('path').join(__dirname, '../../coverage/ngx-quill'), 24 | reporters: [ 25 | { type: 'html', subdir: 'report-html' }, 26 | { type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' }, 27 | { type: 'text-summary', subdir: '.', file: 'text-summary.txt' } 28 | ], 29 | check: { 30 | global: { 31 | statements: 80, 32 | branches: 80, 33 | functions: 80, 34 | lines: 80 35 | } 36 | } 37 | }, 38 | reporters: ['progress', 'kjhtml', 'coverage'], 39 | port: 9876, 40 | colors: true, 41 | logLevel: config.LOG_INFO, 42 | autoWatch: true, 43 | browsers: ['FirefoxHeadless'], 44 | customLaunchers: { 45 | FirefoxHeadless: { 46 | base: 'Firefox', 47 | flags: [ 48 | '-headless' 49 | ], 50 | prefs: { 51 | 'network.proxy.type': 0 52 | } 53 | } 54 | }, 55 | singleRun: false, 56 | restartOnFileChange: true 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular-stl-model-viewer", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-stl-model-viewer", 3 | "license": "MIT", 4 | "version": "14.0.0", 5 | "author": "Marcus Kirsch & Bengt Weiße", 6 | "description": "Angular component for rendering a STL model", 7 | "bugs": "https://github.com/codaline-io/angular-stl-model-viewer/issues", 8 | "homepage": "https://github.com/codaline-io/angular-stl-model-viewer", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/codaline-io/angular-stl-model-viewer" 12 | }, 13 | "engines": { 14 | "node": "^18.19.1 || ^20.11.1 || >=22.0.0" 15 | }, 16 | "peerDependencies": { 17 | "@angular/common": "^20.0.0", 18 | "@angular/core": "^20.0.0", 19 | "rxjs": "^7.8.0", 20 | "three": "^0.155.0", 21 | "zone.js": "~0.15.0" 22 | }, 23 | "dependencies": { 24 | "tslib": "^2.8.1" 25 | }, 26 | "keywords": [ 27 | "angular", 28 | "stl", 29 | "threejs", 30 | "three", 31 | "webGL", 32 | "component", 33 | "viewer" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/src/lib/angular-stl-model-viewer.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, OnInit, ViewChild } from '@angular/core' 3 | import { ComponentFixture, TestBed } from '@angular/core/testing' 4 | 5 | import * as THREE from 'three' 6 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 7 | 8 | import { StlModelViewerComponent } from './angular-stl-model-viewer.component' 9 | import { StlModelViewerModule } from './angular-stl-model-viewer.module' 10 | 11 | @Component({ 12 | imports: [StlModelViewerModule], 13 | template: '' 14 | }) 15 | class TestComponent implements OnInit { 16 | @ViewChild(StlModelViewerComponent, { 17 | static: true 18 | }) stlModelViewerCmp: StlModelViewerComponent | null = null 19 | 20 | isRendered = false 21 | stlModelViewerCmpElement: HTMLCanvasElement | null = null 22 | models = ['test.stl'] 23 | constructor(private eleRef: ElementRef) {} 24 | 25 | ngOnInit() { 26 | this.stlModelViewerCmpElement = this.eleRef.nativeElement.querySelector('stl-model-viewer') 27 | } 28 | } 29 | 30 | describe('StlModelViewerComponent', () => { 31 | let hostFixture: ComponentFixture 32 | let component: StlModelViewerComponent 33 | 34 | beforeEach(() => { 35 | TestBed.configureTestingModule({ 36 | declarations: [], 37 | imports: [ 38 | StlModelViewerComponent 39 | ], 40 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 41 | }).compileComponents() 42 | }) 43 | 44 | beforeEach(() => { 45 | hostFixture = TestBed.createComponent(TestComponent) 46 | component = hostFixture.componentInstance.stlModelViewerCmp as StlModelViewerComponent 47 | 48 | // Mocks stlLoader.load to work without existing stl model 49 | spyOn(component.stlLoader, 'load').and.callFake((_path: string, cb: any) => { 50 | cb(new THREE.BufferGeometry()) 51 | }) 52 | }) 53 | 54 | describe('sets default values', () => { 55 | beforeEach(() => { 56 | // Runs first change detection that triggers ngOnInit 57 | hostFixture.detectChanges() 58 | }) 59 | 60 | it('sets default renderer options', () => { 61 | expect(component.renderer.getPixelRatio()).toBe(window.devicePixelRatio) 62 | expect(component.renderer.shadowMap.enabled).toBe(true) 63 | }) 64 | 65 | it('sets default light position', () => { 66 | expect(component.light.position).toEqual(new THREE.Vector3(1, 1, 2)) 67 | }) 68 | 69 | it('sets default camera position', () => { 70 | expect(component.camera.position.x).toEqual(3) 71 | expect(component.camera.position.y).toEqual(3) 72 | expect(Math.floor(component.camera.position.z)).toEqual(3) 73 | }) 74 | 75 | it('sets default scene background', () => { 76 | expect(component.scene.background).toEqual(new THREE.Color(0xffffff)) 77 | }) 78 | }) 79 | 80 | describe('#ngOnInit', () => { 81 | it('logs error if webgl is not available', () => { 82 | component.hasWebGL = false 83 | spyOn(console, 'error').and.callThrough() 84 | spyOn(component, 'createMesh') 85 | 86 | expect(component.ngOnInit()).toBeUndefined() 87 | expect(console.error).toHaveBeenCalled() 88 | expect(component.createMesh).not.toHaveBeenCalled() 89 | }) 90 | 91 | it('adds light to camera', async () => { 92 | spyOn(component.camera, 'add') 93 | 94 | await component.ngOnInit() 95 | hostFixture.detectChanges() 96 | 97 | expect(component.camera.add).toHaveBeenCalledWith(component.light) 98 | }) 99 | 100 | it('adds camera to scene', async () => { 101 | spyOn(component.scene, 'add') 102 | 103 | await component.ngOnInit() 104 | hostFixture.detectChanges() 105 | 106 | expect(component.scene.add).toHaveBeenCalledWith(component.camera) 107 | }) 108 | 109 | it('sets window event listener', async () => { 110 | spyOn(window, 'addEventListener') 111 | 112 | await component.ngOnInit() 113 | hostFixture.detectChanges() 114 | 115 | expect(window.addEventListener).toHaveBeenCalled() 116 | expect(window.addEventListener).toHaveBeenCalledWith('resize', component.onWindowResize, false) 117 | }) 118 | 119 | it('sets initial sizes and camera aspec ratio', async () => { 120 | spyOn(component, 'setSizes') 121 | 122 | await component.ngOnInit() 123 | hostFixture.detectChanges() 124 | 125 | expect(component.setSizes).toHaveBeenCalled() 126 | }) 127 | 128 | it('adds default controls and sets control options', async () => { 129 | await component.ngOnInit() 130 | hostFixture.detectChanges() 131 | 132 | if (component.controls) { 133 | expect(component.controls.enableZoom).toBe(true) 134 | expect(component.controls.minDistance).toBe(1) 135 | expect(component.controls.maxDistance).toBe(7) 136 | expect(component.controls.hasEventListener('change', component.render)).toBeTruthy() 137 | } 138 | else { 139 | expect(component.controls).not.toBeNull() 140 | } 141 | }) 142 | 143 | it('calls createMesh', async () => { 144 | const fakeMesh = new THREE.Mesh() 145 | spyOn(component, 'createMesh').and.callFake(() => Promise.resolve(fakeMesh)) 146 | spyOn(component.scene, 'add').and.callThrough() 147 | spyOn(component, 'render').and.callThrough() 148 | 149 | await component.ngOnInit() 150 | hostFixture.detectChanges() 151 | 152 | expect(component.createMesh).toHaveBeenCalledWith(component.stlModels[0], undefined) 153 | const el = hostFixture.componentInstance.stlModelViewerCmpElement as HTMLCanvasElement 154 | expect(!!(el.querySelector('canvas'))).toBe(true) 155 | expect(component.render).toHaveBeenCalled() 156 | expect(component.isRendered).toBe(true) 157 | expect(hostFixture.componentInstance.isRendered).toBe(true) 158 | }) 159 | }) 160 | 161 | describe('#ngOnDestroy', () => { 162 | it('remove window event listener', () => { 163 | spyOn(window, 'removeEventListener') 164 | 165 | component.ngOnDestroy() 166 | hostFixture.detectChanges() 167 | 168 | expect(window.removeEventListener).toHaveBeenCalled() 169 | expect(window.removeEventListener).toHaveBeenCalledWith('resize', component.onWindowResize, false) 170 | }) 171 | 172 | it('cleans up controls', () => { 173 | component.controls = new OrbitControls(component.camera, component.renderer.domElement) 174 | 175 | spyOn(component.controls, 'dispose').and.callThrough() 176 | spyOn(component.controls, 'removeEventListener').and.callThrough() 177 | 178 | component.ngOnDestroy() 179 | hostFixture.detectChanges() 180 | 181 | expect(component.controls.dispose).toHaveBeenCalled() 182 | expect(component.controls.removeEventListener).toHaveBeenCalled() 183 | }) 184 | 185 | it('removes scene children', () => { 186 | spyOn(component.scene, 'remove').and.callThrough() 187 | const object3d = new THREE.Object3D() 188 | component.scene.add(object3d) 189 | 190 | component.ngOnDestroy() 191 | hostFixture.detectChanges() 192 | 193 | expect(component.scene.remove).toHaveBeenCalled() 194 | expect(component.scene.remove).toHaveBeenCalledWith(object3d) 195 | }) 196 | }) 197 | 198 | describe('#createMesh', () => { 199 | it('sets correct options', async () => { 200 | component.meshOptions = [{ 201 | castShadow: false, 202 | position: new THREE.Vector3(1, 1, 1), 203 | receiveShadow: false, 204 | scale: new THREE.Vector3(2, 2, 2), 205 | up: new THREE.Vector3(3, 3, 3) 206 | }] 207 | 208 | const mesh = await component.createMesh('test.stl', component.meshOptions[0]) 209 | hostFixture.detectChanges() 210 | 211 | expect(mesh.position).toEqual(new THREE.Vector3(1, 1, 1)) 212 | expect(mesh.scale).toEqual(new THREE.Vector3(2, 2, 2)) 213 | expect(mesh.up).toEqual(new THREE.Vector3(3, 3, 3)) 214 | expect(mesh.receiveShadow).toBe(false) 215 | expect(mesh.castShadow).toBe(false) 216 | }) 217 | 218 | it('sets default options', async () => { 219 | const mesh = await component.createMesh('test.stl') 220 | hostFixture.detectChanges() 221 | 222 | expect(mesh.position).toEqual(new THREE.Vector3(0, 0, 0)) 223 | expect(mesh.scale).toEqual(new THREE.Vector3(0.03, 0.03, 0.03)) 224 | expect(mesh.receiveShadow).toBe(true) 225 | expect(mesh.castShadow).toBe(true) 226 | }) 227 | }) 228 | 229 | describe('#render', () => { 230 | it('rerenders scene with camera', () => { 231 | spyOn(component.renderer, 'render') 232 | 233 | component.render() 234 | hostFixture.detectChanges() 235 | 236 | expect(component.renderer.render).toHaveBeenCalledWith(component.scene, component.camera) 237 | }) 238 | }) 239 | 240 | describe('#onWindowResize', () => { 241 | it('calls set sizes and render', () => { 242 | spyOn(component, 'setSizes').and.callThrough() 243 | spyOn(component, 'render') 244 | 245 | component.onWindowResize() 246 | hostFixture.detectChanges() 247 | 248 | expect(component.render).toHaveBeenCalled() 249 | expect(component.setSizes).toHaveBeenCalled() 250 | }) 251 | }) 252 | }) 253 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/src/lib/angular-stl-model-viewer.component.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { 3 | ChangeDetectionStrategy, 4 | ChangeDetectorRef, 5 | Component, 6 | ElementRef, 7 | EventEmitter, 8 | Input, 9 | NgZone, 10 | OnDestroy, 11 | OnInit, 12 | Output 13 | } from '@angular/core' 14 | 15 | import * as THREE from 'three' 16 | 17 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 18 | import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js' 19 | 20 | import { Vector3 } from 'three' 21 | 22 | export interface MeshOptions { 23 | castShadow?: boolean 24 | position?: THREE.Vector3 25 | receiveShadow?: boolean 26 | scale?: THREE.Vector3 27 | up?: THREE.Vector3 28 | userData?: Record 29 | visible?: boolean 30 | } 31 | 32 | const defaultMeshOptions = { 33 | castShadow: true, 34 | position: new THREE.Vector3(0, 0, 0), 35 | receiveShadow: true, 36 | scale: new THREE.Vector3(0.03, 0.03, 0.03) 37 | } 38 | 39 | const isWebGLAvailable = () => { 40 | try { 41 | const canvas = document.createElement('canvas') 42 | return !!( 43 | window.WebGLRenderingContext 44 | && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')) 45 | ) 46 | } 47 | catch { 48 | return false 49 | } 50 | } 51 | 52 | @Component({ 53 | changeDetection: ChangeDetectionStrategy.OnPush, 54 | selector: 'stl-model-viewer', 55 | styles: [ 56 | ` 57 | :host { 58 | width: 100%; 59 | height: 100%; 60 | display: block; 61 | } 62 | ` 63 | ], 64 | template: '' 65 | }) 66 | export class StlModelViewerComponent implements OnInit, OnDestroy { 67 | @Input() stlModels: string[] = [] 68 | @Input() stlModelFiles: string[] = [] 69 | @Input() hasControls = true 70 | @Input() camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera( 71 | 35, 72 | window.innerWidth / window.innerHeight, 73 | 1, 74 | 15 75 | ) 76 | 77 | @Input() cameraTarget: THREE.Vector3 = new THREE.Vector3(0, 0, 0) 78 | @Input() light: THREE.Light = new THREE.PointLight(0xffffff, 80) 79 | @Input() material: THREE.Material = new THREE.MeshPhongMaterial({ 80 | color: 0x999999, 81 | shininess: 400, 82 | specular: 0x222222 83 | }) 84 | 85 | @Input() scene: THREE.Scene = new THREE.Scene() 86 | @Input() renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({ 87 | antialias: true 88 | }) 89 | 90 | @Input() controls: any | null = null 91 | @Input() meshOptions: MeshOptions[] = [] 92 | @Input() centered = true 93 | 94 | @Output() rendered = new EventEmitter() 95 | 96 | hasWebGL = isWebGLAvailable() 97 | meshGroup = new THREE.Object3D() 98 | isRendered = false 99 | showStlModel = true 100 | stlLoader = new STLLoader() 101 | middle = new THREE.Vector3() 102 | 103 | constructor( 104 | private cdr: ChangeDetectorRef, 105 | private eleRef: ElementRef, 106 | private ngZone: NgZone 107 | ) { 108 | this.cdr.detach() 109 | // default light position 110 | this.light.position.set(1, 1, 2) 111 | 112 | // default camera position 113 | this.camera.position.set(3, 3, 3) 114 | 115 | // default scene background 116 | this.scene.background = new THREE.Color(0xffffff) 117 | 118 | // default renderer options 119 | this.renderer.setPixelRatio(window.devicePixelRatio) 120 | this.renderer.shadowMap.enabled = true 121 | } 122 | 123 | ngOnInit() { 124 | if (!this.hasWebGL) { 125 | console.error( 126 | 'stl-model-viewer: Seems like your system does not support webgl.' 127 | ) 128 | return 129 | } 130 | 131 | this.ngZone.runOutsideAngular(() => { 132 | this.init() 133 | }) 134 | } 135 | 136 | ngOnDestroy() { 137 | window.removeEventListener('resize', this.onWindowResize, false) 138 | 139 | this.meshGroup.children.forEach(child => this.meshGroup.remove(child)) 140 | this.scene.children.forEach(child => this.scene.remove(child)) 141 | this.camera.remove(this.light) 142 | 143 | if (this.material) { 144 | this.material.dispose() 145 | } 146 | 147 | if (this.controls) { 148 | this.controls.removeEventListener('change', this.render) 149 | this.controls.dispose() 150 | } 151 | 152 | if (this.eleRef && this.eleRef.nativeElement.children.length > 1) { 153 | this.eleRef.nativeElement.removeChild(this.renderer.domElement) 154 | } 155 | this.renderer.renderLists.dispose() 156 | this.renderer.domElement.remove() 157 | 158 | this.renderer.dispose() 159 | } 160 | 161 | async createMesh( 162 | path: string, 163 | meshOptions: MeshOptions = {}, 164 | parse = false 165 | ): Promise { 166 | let geometry: THREE.BufferGeometry = null 167 | if (parse) { 168 | geometry = this.stlLoader.parse(path) 169 | } 170 | else { 171 | geometry = await this.stlLoader.loadAsync(path) 172 | } 173 | this.material.shininess = 100 174 | const mesh = new THREE.Mesh(geometry, this.material) 175 | 176 | if (this.centered) { 177 | geometry.computeBoundingBox() 178 | geometry.boundingBox.getCenter(this.middle) 179 | mesh.geometry.applyMatrix4( 180 | new THREE.Matrix4().makeTranslation( 181 | -this.middle.x, 182 | -this.middle.y, 183 | -this.middle.z 184 | ) 185 | ) 186 | } 187 | 188 | const vectorOptions = ['position', 'scale', 'up'] 189 | const options = Object.assign({}, defaultMeshOptions, meshOptions) 190 | 191 | Object.getOwnPropertyNames(options).forEach((option) => { 192 | if (vectorOptions.indexOf(option) > -1) { 193 | const vector = options[option] as Vector3 194 | const meshVectorOption = mesh[option] as Vector3 195 | meshVectorOption.set(vector.x, vector.y, vector.z) 196 | } 197 | else { 198 | mesh[option] = options[option] 199 | } 200 | }) 201 | 202 | return mesh 203 | } 204 | 205 | render = () => { 206 | this.renderer.render(this.scene, this.camera) 207 | } 208 | 209 | setSizes() { 210 | const width = this.eleRef.nativeElement.offsetWidth 211 | const height = this.eleRef.nativeElement.offsetHeight 212 | 213 | this.camera.aspect = width / height 214 | this.camera.updateProjectionMatrix() 215 | 216 | this.renderer.setSize(width, height) 217 | } 218 | 219 | onWindowResize = () => { 220 | this.setSizes() 221 | this.render() 222 | } 223 | 224 | private async init() { 225 | this.camera.add(this.light) 226 | this.scene.add(this.camera) 227 | 228 | // use default controls 229 | if (this.hasControls) { 230 | if (!this.controls) { 231 | this.controls = new OrbitControls( 232 | this.camera, 233 | this.renderer.domElement 234 | ) 235 | this.controls.enableZoom = true 236 | this.controls.minDistance = 1 237 | this.controls.maxDistance = 7 238 | } 239 | this.controls.addEventListener('change', this.render) 240 | } 241 | 242 | window.addEventListener('resize', this.onWindowResize, false) 243 | let meshCreations: Promise[] = [] 244 | if (this.stlModels.length > 0) { 245 | meshCreations = this.stlModels.map((modelPath, index) => 246 | this.createMesh(modelPath, this.meshOptions[index]) 247 | ) 248 | } 249 | else { 250 | meshCreations = this.stlModelFiles.map((modelFile, index) => 251 | this.createMesh(modelFile, this.meshOptions[index], true) 252 | ) 253 | } 254 | const meshes: THREE.Object3D[] = await Promise.all(meshCreations) 255 | 256 | meshes.map(mesh => this.meshGroup.add(mesh)) 257 | 258 | this.scene.add(this.meshGroup) 259 | this.eleRef.nativeElement.appendChild(this.renderer.domElement) 260 | this.setSizes() 261 | this.render() 262 | this.ngZone.run(() => { 263 | this.isRendered = true 264 | this.rendered.emit() 265 | this.cdr.detectChanges() 266 | }) 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/src/lib/angular-stl-model-viewer.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { StlModelViewerComponent } from './angular-stl-model-viewer.component' 3 | 4 | @NgModule({ 5 | exports: [StlModelViewerComponent], 6 | imports: [ 7 | StlModelViewerComponent 8 | ] 9 | }) 10 | export class StlModelViewerModule { } 11 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of angular-stl-model-viewer 3 | */ 4 | 5 | export * from './lib/angular-stl-model-viewer.component' 6 | export * from './lib/angular-stl-model-viewer.module' 7 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing' 4 | import { 5 | BrowserTestingModule, 6 | platformBrowserTesting 7 | } from '@angular/platform-browser/testing' 8 | import 'zone.js' 9 | import 'zone.js/testing' 10 | 11 | getTestBed().initTestEnvironment( 12 | BrowserTestingModule, 13 | platformBrowserTesting() 14 | ) 15 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "inlineSources": true, 7 | "types": [], 8 | "lib": [ 9 | "dom", 10 | "es2018" 11 | ] 12 | }, 13 | "angularCompilerOptions": { 14 | "skipTemplateCodegen": true, 15 | "strictMetadataEmit": true, 16 | "enableResourceInlining": true 17 | }, 18 | "exclude": [ 19 | "src/test.ts", 20 | "**/*.spec.ts" 21 | ] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/angular-stl-model-viewer/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/examples/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /projects/examples/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | process.env.CHROME_BIN = require('puppeteer').executablePath() 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | basePath: '', 8 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 9 | plugins: [ 10 | require('karma-jasmine'), 11 | require('karma-firefox-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | preprocessors: { 20 | 'src/**/*.ts': ['coverage'] 21 | }, 22 | coverageReporter: { 23 | dir: require('path').join(__dirname, '../../coverage/ngx-quill'), 24 | reporters: [ 25 | { type: 'html', subdir: 'report-html' }, 26 | { type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' }, 27 | { type: 'text-summary', subdir: '.', file: 'text-summary.txt' } 28 | ] 29 | }, 30 | reporters: ['progress', 'kjhtml', 'coverage'], 31 | port: 9876, 32 | colors: true, 33 | logLevel: config.LOG_INFO, 34 | autoWatch: true, 35 | browsers: ['FirefoxHeadless'], 36 | customLaunchers: { 37 | FirefoxHeadless: { 38 | base: 'Firefox', 39 | flags: [ 40 | '-headless' 41 | ], 42 | prefs: { 43 | 'network.proxy.type': 0 44 | } 45 | } 46 | }, 47 | singleRun: false, 48 | restartOnFileChange: true 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /projects/examples/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | stl-model-viewer { 2 | width: 100%; 3 | height: 100%; 4 | display: block; 5 | } 6 | -------------------------------------------------------------------------------- /projects/examples/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

{{title}}

2 |
3 | 4 | 5 | 6 |

Custom controls, light, scene, camera, renderer

7 | 8 |
9 | -------------------------------------------------------------------------------- /projects/examples/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing' 2 | import { AppComponent } from './app.component' 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | 8 | imports: [AppComponent] 9 | }).compileComponents() 10 | })) 11 | 12 | it('should create the app', () => { 13 | const fixture = TestBed.createComponent(AppComponent) 14 | const app = fixture.componentInstance 15 | expect(app).toBeTruthy() 16 | }) 17 | 18 | it('should have as title "Examples"', () => { 19 | const fixture = TestBed.createComponent(AppComponent) 20 | const app = fixture.componentInstance 21 | expect(app.title).toEqual('Examples') 22 | }) 23 | 24 | it('should render stl-model-viewer', async () => { 25 | const fixture = TestBed.createComponent(AppComponent) 26 | fixture.detectChanges() 27 | const compiled = fixture.nativeElement 28 | expect(compiled.querySelectorAll('stl-model-viewer').length).toBe(3) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /projects/examples/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 3 | 4 | import { BrowserModule } from '@angular/platform-browser' 5 | import { StlModelViewerModule } from 'angular-stl-model-viewer' 6 | import * as THREE from 'three' 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | imports: [ 11 | BrowserModule, 12 | StlModelViewerModule 13 | ], 14 | styleUrls: ['./app.component.css'], 15 | templateUrl: './app.component.html' 16 | }) 17 | export class AppComponent { 18 | title = 'Examples' 19 | 20 | renderer = new THREE.WebGLRenderer({ antialias: true }) 21 | camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15) 22 | controls = new OrbitControls(this.camera, this.renderer.domElement) 23 | scene = new THREE.Scene() 24 | light = new THREE.PointLight(0xffffff, 80) 25 | 26 | constructor() { 27 | this.renderer.setPixelRatio(window.devicePixelRatio) 28 | this.renderer.shadowMap.enabled = true 29 | 30 | // default camera position 31 | this.camera.position.set(3, 3, 3) 32 | 33 | // default light position 34 | this.light.position.set(1, 1, 2) 35 | 36 | // default scene background 37 | this.scene.background = new THREE.Color(0xffffff) 38 | 39 | this.controls.enableZoom = true 40 | this.controls.minDistance = 1 41 | this.controls.maxDistance = 7 42 | 43 | this.controls.update() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/examples/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codaline-io/angular-stl-model-viewer/ab7d2c113aad599ec544f8f9e6bcf0436ede13b3/projects/examples/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/examples/src/assets/peter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codaline-io/angular-stl-model-viewer/ab7d2c113aad599ec544f8f9e6bcf0436ede13b3/projects/examples/src/assets/peter.stl -------------------------------------------------------------------------------- /projects/examples/src/assets/strap.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codaline-io/angular-stl-model-viewer/ab7d2c113aad599ec544f8f9e6bcf0436ede13b3/projects/examples/src/assets/strap.stl -------------------------------------------------------------------------------- /projects/examples/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | } 4 | -------------------------------------------------------------------------------- /projects/examples/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | } 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /projects/examples/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codaline-io/angular-stl-model-viewer/ab7d2c113aad599ec544f8f9e6bcf0436ede13b3/projects/examples/src/favicon.ico -------------------------------------------------------------------------------- /projects/examples/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Examples 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/examples/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core' 2 | 3 | import { bootstrapApplication } from '@angular/platform-browser' 4 | import { AppComponent } from './app/app.component' 5 | import { environment } from './environments/environment' 6 | 7 | if (environment.production) { 8 | enableProdMode() 9 | } 10 | 11 | bootstrapApplication(AppComponent, { 12 | providers: [] 13 | }) 14 | .catch(err => console.error(err)) 15 | -------------------------------------------------------------------------------- /projects/examples/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js' // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /projects/examples/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /projects/examples/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js' 4 | import 'zone.js/testing' 5 | 6 | import { getTestBed } from '@angular/core/testing' 7 | import { 8 | BrowserTestingModule, 9 | platformBrowserTesting 10 | } from '@angular/platform-browser/testing' 11 | 12 | getTestBed().initTestEnvironment( 13 | BrowserTestingModule, 14 | platformBrowserTesting() 15 | ) 16 | -------------------------------------------------------------------------------- /projects/examples/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts", 13 | "src/environments/environment.prod.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /projects/examples/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "useDefineForClassFields": false, 14 | "target": "ES2022", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ], 19 | "paths": { 20 | "angular-stl-model-viewer": [ 21 | "dist/angular-stl-model-viewer/angular-stl-model-viewer", 22 | "dist/angular-stl-model-viewer" 23 | ] 24 | } 25 | }, 26 | "angularCompilerOptions": { 27 | "fullTemplateTypeCheck": true, 28 | "strictInjectionParameters": true 29 | } 30 | } 31 | --------------------------------------------------------------------------------