Skip to content

Commit

Permalink
Feature/475/export 3d printer files (#2561)
Browse files Browse the repository at this point in the history
* WIP: Download stl file #475

* WIP: Add Angular button for 3D file export #475

* WIP: Fix naming issue and Download function #475

* Add material design icons #475

* Fix Stl file download button with correct filename #475

* Fix button size and icon #475

* Revert download button #475

* Fix unused store variable #475

* Fix button look to match other buttons #475

* Add test file #475

* Refactor component file #475

* Refactor and clean up #475

* Add chnagelog entry #475

* Fix css and subscribe issues #475

* fix: re-add style but nest class names

ref #475

* test: add basic test for template

ref #475

* Fix typo #475

* Improve Tests #475

* Improve test naming #475

Co-authored-by: Torsten Knauf <[email protected]>
  • Loading branch information
IhsenBouallegue and Torsten Knauf authored Dec 20, 2021
1 parent 131bed9 commit e744877
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 4 deletions.
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/)

## [unreleased] (Added 🚀 | Changed | Removed 🗑 | Fixed 🐞 | Chore 👨‍💻 👩‍💻)

## Changed
### Added 🚀

- Add 3D export feature for 3D printing [#2561](https://github.com/MaibornWolff/codecharta/pull/2561). <br>
<img src="https://user-images.githubusercontent.com/48621967/146173663-e0ea177e-6ed8-4ddb-bd11-410415541e9f.png" height="32px" alt="two menu buttons"/> <br>
<img src="https://user-images.githubusercontent.com/48621967/146174397-42a6e475-ed2f-47c0-ba9c-4f8304d53399.png" height="256px" alt="two menu buttons"/>

### Changed

- Replace all color pickers in the context of ongoing Angular migration [#2560](https://github.com/MaibornWolff/codecharta/pull/2560).

## Fixed 🐞
### Fixed 🐞

- Fix showing names of custom configs properly [#2557](https://github.com/MaibornWolff/codecharta/pull/2557)

## Chore 👨‍💻 👩‍💻
### Chore 👨‍💻 👩‍💻

- Migrate legend panel to Angular [#2560](https://github.com/MaibornWolff/codecharta/pull/2560).

Expand Down
4 changes: 4 additions & 0 deletions visualization/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { MatchingFilesCounterComponent } from "./codeCharta/ui/matchingFilesCoun
import { MatchingFilesCounterModule } from "./codeCharta/ui/matchingFilesCounter/matchingFilesCounter.module"
import { AttributeSideBarModule } from "./codeCharta/ui/attributeSideBar/attributeSideBar.module"
import { AttributeSideBarComponent } from "./codeCharta/ui/attributeSideBar/attributeSideBar.component"
import { Export3DMapButtonComponent } from "./codeCharta/ui/export3DMapButton/export3DMapButton.component"
import { Export3DMapButtonModule } from "./codeCharta/ui/export3DMapButton/export3DMapButton.module"
import { LabelledColorPickerComponent } from "./codeCharta/ui/labelledColorPicker/labelledColorPicker.component"
import { LegendPanelComponent } from "./codeCharta/ui/legendPanel/legendPanel.component"
import { LegendPanelModule } from "./codeCharta/ui/legendPanel/legendPanel.module"
Expand All @@ -26,6 +28,7 @@ import { MarkFolderColorPickerComponent } from "./codeCharta/ui/nodeContextMenu/
MapTreeViewModule,
MatchingFilesCounterModule,
AttributeSideBarModule,
Export3DMapButtonModule,
LegendPanelModule,
ColorPickerForMapColorModule,
MarkFolderColorPickerModule
Expand All @@ -35,6 +38,7 @@ import { MarkFolderColorPickerComponent } from "./codeCharta/ui/nodeContextMenu/
MapTreeViewComponent,
MatchingFilesCounterComponent,
AttributeSideBarComponent,
Export3DMapButtonComponent,
LegendPanelComponent,
LabelledColorPickerComponent,
ColorPickerForMapColorComponent,
Expand Down
1 change: 1 addition & 0 deletions visualization/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "angular-material"
import "./codeCharta/codeCharta.module"
import "angular-sanitize"
import "./app.scss"
import "material-icons/iconfont/material-icons.css"

angular.module("app", ["app.codeCharta", "ngMaterial", "ngSanitize"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export class ThreeSceneService implements CodeMapPreRenderServiceSubscriber, Map
mapGeometry: Group
private readonly lights: Group
private mapMesh: CodeMapMesh
/** TODO: Fix temporary workaround as soon as mapMesh can be derived from store */
static mapMeshInstance: CodeMapMesh

private selected: CodeMapBuilding = null
private highlighted: CodeMapBuilding[] = []
Expand Down Expand Up @@ -398,6 +400,7 @@ export class ThreeSceneService implements CodeMapPreRenderServiceSubscriber, Map
setMapMesh(nodes: Node[], mesh: CodeMapMesh) {
const { mapSize } = this.storeService.getState().treeMap
this.mapMesh = mesh
ThreeSceneService.mapMeshInstance = this.mapMesh

this.initFloorLabels(nodes)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<button
mat-mini-fab
class="export-3d-button"
aria-label="3d print map"
(click)="downloadStlFile()"
title="Download stl file for 3D printing"
>
<mat-icon class="button-icon">view_in_ar</mat-icon>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
cc-export-threed-map-button {
margin-left: 8px;
.mat-flat-button.mat-accent,
.mat-raised-button.mat-accent,
.mat-fab.mat-accent,
.mat-mini-fab.mat-accent {
background-color: rgb(0, 150, 136);
}
.button-icon {
font-size: 16px;
line-height: 1.3;
}
span {
padding: 0;
margin: 0;
}
.mat-mini-fab {
height: 25px;
width: 25px;

.mat-button-wrapper {
display: block;
padding: 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TestBed } from "@angular/core/testing"
import { Export3DMapButtonComponent } from "./export3DMapButton.component"
import { fireEvent, render } from "@testing-library/angular"
import { Export3DMapButtonModule } from "./export3DMapButton.module"
import { FileDownloader } from "../../util/fileDownloader"
import { ThreeSceneService } from "../codeMap/threeViewer/threeSceneService"
import { CodeMapMesh } from "../codeMap/rendering/codeMapMesh"
import { stubDate } from "../../../../mocks/dateMock.helper"
import { FILE_STATES } from "../../util/dataMocks"

stubDate(new Date(Date.UTC(2018, 11, 14, 9, 39)))
const newDate = "2018-12-14_09-39"

const mockFileStates = FILE_STATES

jest.mock("../../state/store/files/files.selector", () => ({
filesSelector: () => mockFileStates
}))

jest.mock("../../state/selectors/accumulatedData/accumulatedData.selector", () => ({
accumulatedDataSelector: () => ({
unifiedFileMeta: { fileName: "sample" }
})
}))

jest.mock("three/examples/jsm/exporters/STLExporter", () => ({
STLExporter: jest.fn(() => ({ parse: jest.fn().mockImplementation(() => null) }))
}))

describe("Export3DMapButtonComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [Export3DMapButtonModule]
})
})

it("should start download on click", async function () {
const { container } = await render(Export3DMapButtonComponent, { excludeComponentDeclaration: true })
// mock download and therefore only verify the Angular binding.
// A better approach would be, if the component would only fire an action
// and an https://ngrx.io/guide/effects would do the side effect and logic.
// Then we could test the logic in the effect without mocking a lot
// and the component wouldn't need to know anything about store values
// @ts-ignore
const mockedDownload = jest.spyOn(ng.probe(container).componentInstance, "downloadStlFile").mockImplementation(() => null)

const downloadButton = container.querySelector(".export-3d-button")
expect(downloadButton).not.toBe(null)

fireEvent.click(downloadButton)
expect(mockedDownload).toHaveBeenCalledTimes(1)
})
it("should download STL file with the right file name", async function () {
const { container } = await render(Export3DMapButtonComponent, { excludeComponentDeclaration: true })
const downloadButton = container.querySelector(".export-3d-button")

const downloadData = jest.spyOn(FileDownloader, "downloadData").mockImplementation(() => null)
ThreeSceneService.mapMeshInstance = { getThreeMesh: jest.fn() } as unknown as CodeMapMesh

fireEvent.click(downloadButton)

expect(downloadData).toHaveBeenCalledWith(null, `sample_${newDate}.stl`)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import "./export3DMapButton.component.scss"
import { Component, Inject, OnDestroy } from "@angular/core"
import { FileDownloader } from "../../util/fileDownloader"
import { STLExporter } from "three/examples/jsm/exporters/STLExporter"
import { ThreeSceneService } from "../codeMap/threeViewer/threeSceneService"
import { FileNameHelper } from "../../util/fileNameHelper"
import { isDeltaState } from "../../model/files/files.helper"
import { Store } from "../../state/angular-redux/store"
import { accumulatedDataSelector } from "../../state/selectors/accumulatedData/accumulatedData.selector"
import { filesSelector } from "../../state/store/files/files.selector"
import { FileState } from "../../model/files/files"
import { Mesh } from "three"

@Component({
selector: "cc-export-threed-map-button",
template: require("./export3DMapButton.component.html")
})
export class Export3DMapButtonComponent implements OnDestroy {
private fileName: string
private files: FileState[]
private exporter = new STLExporter()
private storeSubscriptions = []
constructor(@Inject(Store) store: Store) {
this.storeSubscriptions.push(
store.select(accumulatedDataSelector).subscribe(accumulatedData => {
this.fileName = accumulatedData.unifiedFileMeta?.fileName
}),
store.select(filesSelector).subscribe(files => {
this.files = files
})
)
}

downloadStlFile() {
const threeMesh: Mesh = ThreeSceneService.mapMeshInstance.getThreeMesh()
const exportedBinaryFile = this.exporter.parse(threeMesh, { binary: true })
const fileName = `${FileNameHelper.getNewFileName(this.fileName, isDeltaState(this.files))}.stl`
FileDownloader.downloadData(exportedBinaryFile, fileName)
}

ngOnDestroy(): void {
for (const subscription of this.storeSubscriptions) subscription.unsubscribe()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommonModule } from "@angular/common"
import { NgModule } from "@angular/core"
import { Export3DMapButtonComponent } from "./export3DMapButton.component"
import { MaterialModule } from "../../../material/material.module"

@NgModule({
imports: [CommonModule, MaterialModule],
declarations: [Export3DMapButtonComponent],
exports: [Export3DMapButtonComponent]
})
export class Export3DMapButtonModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<file-chooser-directive></file-chooser-directive>
<download-button-component ng-show="!$ctrl._viewModel.experimentalFeaturesEnabled"></download-button-component>
<screenshot-button-component></screenshot-button-component>
<cc-export-threed-map-button></cc-export-threed-map-button>

<file-panel-component ng-class="{ hidden: $ctrl._viewModel.isNodeHovered }"></file-panel-component>
<node-path-panel-component ng-class="{ hidden: !$ctrl._viewModel.isNodeHovered }"></node-path-panel-component>
Expand Down
2 changes: 2 additions & 0 deletions visualization/app/codeCharta/ui/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { SortingOptionComponent } from "./sortingOption/sortingOption.component"
import { MatchingFilesCounterComponent } from "./matchingFilesCounter/matchingFilesCounter.component"
import { SortingButtonComponent } from "./sortingButton/sortingButton.component"
import { AttributeTypeSelectorComponent } from "./attributeSideBar/attributeTypeSelector/attributeTypeSelector.component"
import { Export3DMapButtonComponent } from "./export3DMapButton/export3DMapButton.component"
import { LegendPanelComponent } from "./legendPanel/legendPanel.component"

angular
Expand Down Expand Up @@ -93,4 +94,5 @@ angular
.directive("ccMapTreeView", downgradeComponent({ component: MapTreeViewComponent }))
.directive("ccMatchingFilesCounter", downgradeComponent({ component: MatchingFilesCounterComponent }))
.directive("ccAttributeTypeSelector", downgradeComponent({ component: AttributeTypeSelectorComponent }))
.directive("ccExportThreedMapButton", downgradeComponent({ component: Export3DMapButtonComponent }))
.directive("ccLegendPanel", downgradeComponent({ component: LegendPanelComponent }))
3 changes: 2 additions & 1 deletion visualization/app/material/material.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { MatMenuModule } from "@angular/material/menu"
import { MatButtonModule } from "@angular/material/button"
import { MatDividerModule } from "@angular/material/divider"
import { MatTooltipModule } from "@angular/material/tooltip"
import { MatIconModule } from "@angular/material/icon"

const materialModules = [MatSelectModule, MatMenuModule, MatButtonModule, MatDividerModule, MatTooltipModule]
const materialModules = [MatSelectModule, MatMenuModule, MatButtonModule, MatDividerModule, MatTooltipModule, MatIconModule]

@NgModule({
imports: [BrowserAnimationsModule, materialModules],
Expand Down
11 changes: 11 additions & 0 deletions visualization/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions visualization/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"material-icons": "^1.10.4",
"md5": "^2.3.0",
"ngx-color": "^7.3.3",
"nouislider": "^15.0.0",
Expand Down

0 comments on commit e744877

Please sign in to comment.