fix dirty file save logic
parent
132be08553
commit
662ced6edd
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
51
src/Scene.js
51
src/Scene.js
|
@ -36,12 +36,12 @@ export class Scene {
|
||||||
this.sid = 1
|
this.sid = 1
|
||||||
this.mid = 1
|
this.mid = 1
|
||||||
|
|
||||||
this.store = store;
|
|
||||||
this.canvas = document.querySelector('#c');
|
this.canvas = document.querySelector('#c');
|
||||||
|
|
||||||
this.rect = this.canvas.getBoundingClientRect().toJSON()
|
this.rect = this.canvas.getBoundingClientRect().toJSON()
|
||||||
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
|
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
|
||||||
|
this.store = store
|
||||||
|
|
||||||
const size = 1;
|
const size = 1;
|
||||||
const near = 0;
|
const near = 0;
|
||||||
|
@ -178,11 +178,6 @@ export class Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
saveScene() {
|
|
||||||
return JSON.stringify([id, this.sid, this.mid, this.store.getState().treeEntries])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
clearScene() {
|
clearScene() {
|
||||||
const deleted = this.obj3d.children.splice(1)
|
const deleted = this.obj3d.children.splice(1)
|
||||||
|
|
||||||
|
@ -240,7 +235,6 @@ export class Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.dispatch({ type: 'restore-state', state })
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,17 +256,10 @@ export class Scene {
|
||||||
|
|
||||||
this.obj3d.add(mesh)
|
this.obj3d.add(mesh)
|
||||||
|
|
||||||
this.store.dispatch({ type: 'rx-extrusion', mesh, sketchId: sketch.obj3d.name })
|
return mesh
|
||||||
|
|
||||||
if (this.activeSketch == sketch) {
|
|
||||||
this.store.dispatch({ type: 'finish-sketch' })
|
|
||||||
sketch.deactivate()
|
|
||||||
}
|
|
||||||
this.render()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolOp(m1, m2, op, refresh = false) {
|
boolOp(m1, m2, op) {
|
||||||
let bspA = CSG.fromMesh(m1)
|
let bspA = CSG.fromMesh(m1)
|
||||||
let bspB = CSG.fromMesh(m2)
|
let bspB = CSG.fromMesh(m2)
|
||||||
m1.visible = false
|
m1.visible = false
|
||||||
|
@ -312,33 +299,16 @@ export class Scene {
|
||||||
|
|
||||||
mesh.add(vertices)
|
mesh.add(vertices)
|
||||||
|
|
||||||
if (!refresh) {
|
return mesh
|
||||||
sc.obj3d.add(mesh)
|
|
||||||
|
|
||||||
this.store.dispatch({
|
|
||||||
type: 'set-entry-visibility', obj: {
|
|
||||||
[m1.name]: false,
|
|
||||||
[m2.name]: false,
|
|
||||||
[mesh.name]: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.store.dispatch({
|
|
||||||
type: 'rx-boolean', mesh, deps: [m1.name, m2.name]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return mesh
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshNode(id) {
|
refreshNode(id, { byId, tree }) {
|
||||||
let curId
|
let curId
|
||||||
let que = [id]
|
let que = [id]
|
||||||
let idx = 0
|
let idx = 0
|
||||||
// let newNodes = {}
|
// let newNodes = {}
|
||||||
|
|
||||||
const { byId, tree } = this.store.getState().treeEntries
|
|
||||||
while (idx < que.length) {
|
while (idx < que.length) {
|
||||||
curId = que[idx++]
|
curId = que[idx++]
|
||||||
|
|
||||||
|
@ -348,7 +318,7 @@ export class Scene {
|
||||||
if (info.length == 2) {
|
if (info.length == 2) {
|
||||||
newNode = extrude(byId[info[0]], info[1])
|
newNode = extrude(byId[info[0]], info[1])
|
||||||
} else if (info.length == 3) {
|
} else if (info.length == 3) {
|
||||||
newNode = this.boolOp(byId[info[0]], byId[info[1]], info[2], true)
|
newNode = this.boolOp(byId[info[0]], byId[info[1]], info[2])
|
||||||
}
|
}
|
||||||
byId[curId].geometry.copy(newNode.geometry)
|
byId[curId].geometry.copy(newNode.geometry)
|
||||||
byId[curId].geometry.parameters = newNode.geometry.parameters // took 2 hours to figure out
|
byId[curId].geometry.parameters = newNode.geometry.parameters // took 2 hours to figure out
|
||||||
|
@ -433,14 +403,9 @@ async function addSketch() {
|
||||||
|
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
|
|
||||||
|
|
||||||
sketch.obj3d.addEventListener('change', this.render);
|
sketch.obj3d.addEventListener('change', this.render);
|
||||||
this.store.dispatch({ type: 'rx-sketch', obj: sketch })
|
|
||||||
|
return sketch
|
||||||
sketch.activate()
|
|
||||||
|
|
||||||
this.render()
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { onHover, onDrag, onPick, onRelease, clearSelection} from './mouseEvents
|
||||||
import { setCoincident, setOrdinate, setTangent } from './constraintEvents'
|
import { setCoincident, setOrdinate, setTangent } from './constraintEvents'
|
||||||
import { get3PtArc } from './drawArc'
|
import { get3PtArc } from './drawArc'
|
||||||
import { replacer, reviver } from './utils'
|
import { replacer, reviver } from './utils'
|
||||||
import { AxesHelper } from './sketchAxes'
|
|
||||||
import { drawDimension, _onMoveDimension, setDimLines, updateDim } from './drawDimension';
|
import { drawDimension, _onMoveDimension, setDimLines, updateDim } from './drawDimension';
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +18,6 @@ class Sketch {
|
||||||
|
|
||||||
constructor(scene, preload) {
|
constructor(scene, preload) {
|
||||||
|
|
||||||
|
|
||||||
// [0]:x, [1]:y, [2]:z
|
// [0]:x, [1]:y, [2]:z
|
||||||
this.ptsBuf = new Float32Array(this.max_pts * 3).fill(NaN)
|
this.ptsBuf = new Float32Array(this.max_pts * 3).fill(NaN)
|
||||||
|
|
||||||
|
@ -96,7 +94,6 @@ class Sketch {
|
||||||
this.camera = scene.camera
|
this.camera = scene.camera
|
||||||
this.canvas = scene.canvas
|
this.canvas = scene.canvas
|
||||||
this.rect = scene.rect
|
this.rect = scene.rect
|
||||||
this.store = scene.store;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,15 +150,28 @@ class Sketch {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setClean() {
|
||||||
|
this.hasChanged = false
|
||||||
|
this.idOnActivate = id
|
||||||
|
this.c_idOnActivate = this.c_id
|
||||||
|
|
||||||
|
const changeDetector = (e) => {
|
||||||
|
if (this.selected.length && e.buttons) {
|
||||||
|
this.canvas.removeEventListener('pointermove', changeDetector)
|
||||||
|
this.hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.addEventListener('pointermove', changeDetector)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
console.log('activate sketch')
|
|
||||||
window.addEventListener('keydown', this.onKeyPress)
|
window.addEventListener('keydown', this.onKeyPress)
|
||||||
this.canvas.addEventListener('pointerdown', this.onPick)
|
this.canvas.addEventListener('pointerdown', this.onPick)
|
||||||
this.canvas.addEventListener('pointermove', this.onHover)
|
this.canvas.addEventListener('pointermove', this.onHover)
|
||||||
|
|
||||||
|
|
||||||
this.store.dispatch({ type: 'set-active-sketch', activeSketchId: this.obj3d.name })
|
|
||||||
|
|
||||||
this.setDimLines()
|
this.setDimLines()
|
||||||
|
|
||||||
this.obj3d.traverse(e => e.layers.enable(2))
|
this.obj3d.traverse(e => e.layers.enable(2))
|
||||||
|
@ -172,21 +182,12 @@ class Sketch {
|
||||||
|
|
||||||
window.sketcher = this
|
window.sketcher = this
|
||||||
|
|
||||||
// overkill but good solution if this check was more costly
|
this.setClean()
|
||||||
this.hasChanged = false
|
|
||||||
this.idOnActivate = id
|
|
||||||
this.c_idOnActivate = this.c_id
|
|
||||||
// console.log(this,this.selected)
|
|
||||||
const changeDetector = (e) => {
|
|
||||||
if (this.selected.length && e.buttons) {
|
|
||||||
this.canvas.removeEventListener('pointermove', changeDetector)
|
|
||||||
this.hasChanged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.canvas.addEventListener('pointermove', changeDetector)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
|
console.log('deactivate')
|
||||||
window.removeEventListener('keydown', this.onKeyPress)
|
window.removeEventListener('keydown', this.onKeyPress)
|
||||||
this.canvas.removeEventListener('pointerdown', this.onPick)
|
this.canvas.removeEventListener('pointerdown', this.onPick)
|
||||||
this.canvas.removeEventListener('pointermove', this.onHover)
|
this.canvas.removeEventListener('pointermove', this.onHover)
|
||||||
|
|
|
@ -10,34 +10,42 @@ import * as Icon from "./icons";
|
||||||
export const Dialog = () => {
|
export const Dialog = () => {
|
||||||
|
|
||||||
const dialog = useSelector(state => state.ui.dialog)
|
const dialog = useSelector(state => state.ui.dialog)
|
||||||
|
const treeEntries = useSelector(state => state.treeEntries)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const ref = useRef()
|
const ref = useRef()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(dialog)
|
|
||||||
if (!ref.current) return
|
if (!ref.current) return
|
||||||
ref.current.focus()
|
ref.current.focus()
|
||||||
}, [dialog])
|
}, [dialog])
|
||||||
|
|
||||||
const extrude = () => {
|
const extrude = () => {
|
||||||
sc.extrude(dialog.target, ref.current.value)
|
const mesh = sc.extrude(dialog.target, ref.current.value)
|
||||||
sc.render()
|
|
||||||
|
dispatch({ type: 'rx-extrusion', mesh, sketchId: dialog.target.obj3d.name })
|
||||||
|
|
||||||
|
if (sc.activeSketch == dialog.target) {
|
||||||
|
dispatch({ type: 'finish-sketch' })
|
||||||
|
dialog.target.deactivate()
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({ type: "clear-dialog" })
|
dispatch({ type: "clear-dialog" })
|
||||||
|
|
||||||
|
sc.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
const extrudeEdit = () => {
|
const extrudeEdit = () => {
|
||||||
|
|
||||||
|
|
||||||
dialog.target.userData.featureInfo[1] = ref.current.value
|
dialog.target.userData.featureInfo[1] = ref.current.value
|
||||||
|
|
||||||
sc.refreshNode(dialog.target.name)
|
sc.refreshNode(dialog.target.name, treeEntries)
|
||||||
|
dispatch({ type: 'set-modified', status: true })
|
||||||
|
|
||||||
sc.render()
|
|
||||||
dispatch({ type: "clear-dialog" })
|
dispatch({ type: "clear-dialog" })
|
||||||
|
|
||||||
|
sc.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +61,13 @@ export const Dialog = () => {
|
||||||
onClick={extrude}
|
onClick={extrude}
|
||||||
/>
|
/>
|
||||||
<MdClose className="btn w-auto h-full p-3.5 mr-6"
|
<MdClose className="btn w-auto h-full p-3.5 mr-6"
|
||||||
onClick={() => dispatch({ type: "clear-dialog" })}
|
onClick={() => {
|
||||||
|
if (sc.activeSketch == dialog.target) { // if extrude dialog launched from sketch mode we set dialog back to the sketch dialog
|
||||||
|
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||||
|
} else {
|
||||||
|
dispatch({ type: "clear-dialog" })
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
case 'extrude-edit':
|
case 'extrude-edit':
|
||||||
|
@ -79,7 +93,9 @@ export const Dialog = () => {
|
||||||
|| sc.activeSketch.idOnActivate != id
|
|| sc.activeSketch.idOnActivate != id
|
||||||
|| sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id
|
|| sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id
|
||||||
) {
|
) {
|
||||||
sc.refreshNode(sc.activeSketch.obj3d.name)
|
sc.refreshNode(sc.activeSketch.obj3d.name, treeEntries)
|
||||||
|
|
||||||
|
dispatch({ type: 'set-modified', status: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ type: 'finish-sketch' })
|
dispatch({ type: 'finish-sketch' })
|
||||||
|
@ -96,9 +112,10 @@ export const Dialog = () => {
|
||||||
|| sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id
|
|| sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id
|
||||||
) {
|
) {
|
||||||
dispatch({ type: "restore-sketch" })
|
dispatch({ type: "restore-sketch" })
|
||||||
} else {
|
// dispatch({ type: 'set-modified', status: false })
|
||||||
dispatch({ type: 'finish-sketch' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch({ type: 'finish-sketch' })
|
||||||
|
|
||||||
sc.activeSketch.deactivate()
|
sc.activeSketch.deactivate()
|
||||||
sc.render()
|
sc.render()
|
||||||
|
|
|
@ -16,12 +16,14 @@ var tzoffset = (new Date()).getTimezoneOffset() * 60000;
|
||||||
|
|
||||||
export function STLExport(filename) {
|
export function STLExport(filename) {
|
||||||
if (sc.selected[0] && sc.selected[0].userData.type == 'mesh') {
|
if (sc.selected[0] && sc.selected[0].userData.type == 'mesh') {
|
||||||
|
|
||||||
const result = STLexp.parse(sc.selected[0], { binary: true });
|
const result = STLexp.parse(sc.selected[0], { binary: true });
|
||||||
|
|
||||||
const time = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -5).replace(/:/g, '-');
|
const time = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -5).replace(/:/g, '-');
|
||||||
|
|
||||||
saveLegacy(new Blob([result], { type: 'model/stl' }), `${filename}_${time}.stl`);
|
saveLegacy(new Blob([result], { type: 'model/stl' }), `${filename}_${time}.stl`);
|
||||||
|
} else {
|
||||||
|
alert('please select one body to export')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ export async function saveFileAs(file, dispatch) {
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
types: [{
|
types: [{
|
||||||
// description: 'Text file',
|
description: 'Text file',
|
||||||
accept: { 'application/json': ['.json'] },
|
accept: { 'application/json': ['.json'] },
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
@ -114,11 +116,12 @@ export async function openFile(dispatch) {
|
||||||
try {
|
try {
|
||||||
const file = await fileHandle.getFile();
|
const file = await fileHandle.getFile();
|
||||||
const text = await file.text();;
|
const text = await file.text();;
|
||||||
sc.loadState(text)
|
|
||||||
|
dispatch({ type: 'restore-state', state: sc.loadState(text) })
|
||||||
dispatch({ type: 'set-file-handle', fileHandle })
|
dispatch({ type: 'set-file-handle', fileHandle })
|
||||||
// app.setModified(false);
|
|
||||||
// app.setFocus(true);
|
// app.setFocus(true);
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const msg = `An error occured reading ${fileHandle}`;
|
const msg = `An error occured reading ${fileHandle}`;
|
||||||
console.error(msg, ex);
|
console.error(msg, ex);
|
||||||
|
@ -127,3 +130,30 @@ export async function openFile(dispatch) {
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export function confirmDiscard(modified) {
|
||||||
|
if (!modified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const confirmMsg = 'Discard changes? All changes will be lost.';
|
||||||
|
return confirm(confirmMsg);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export async function verifyPermission(fileHandle) {
|
||||||
|
const opts = {
|
||||||
|
mode:'readwrite'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if we already have permission, if so, return true.
|
||||||
|
if (await fileHandle.queryPermission(opts) === 'granted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Request permission to the file, if the user grants permission, return true.
|
||||||
|
if (await fileHandle.requestPermission(opts) === 'granted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The user did nt grant permission, return false.
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -4,43 +4,88 @@ import React, { useEffect, useReducer } from 'react';
|
||||||
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
import { FaEdit, FaFileDownload } from 'react-icons/fa'
|
import { FaEdit } from 'react-icons/fa'
|
||||||
import { MdSave, MdFolder, MdFileUpload, MdInsertDriveFile } from 'react-icons/md'
|
import { MdSave, MdFolder, MdInsertDriveFile } from 'react-icons/md'
|
||||||
import { FaRegFolderOpen, FaFile } from 'react-icons/fa'
|
|
||||||
|
|
||||||
import * as Icon from "./icons";
|
import * as Icon from "./icons";
|
||||||
import { Dialog } from './dialog'
|
import { Dialog } from './dialog'
|
||||||
import { STLExport, saveFile, openFile } from './fileHelpers'
|
import { STLExport, saveFile, openFile, verifyPermission } from './fileHelpers'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const NavBar = () => {
|
export const NavBar = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
|
const sketchActive = useSelector(state => state.ui.sketchActive)
|
||||||
const treeEntriesById = useSelector(state => state.treeEntries.byId)
|
const treeEntries = useSelector(state => state.treeEntries)
|
||||||
const fileHandle = useSelector(state => state.ui.fileHandle)
|
const fileHandle = useSelector(state => state.ui.fileHandle)
|
||||||
|
const modified = useSelector(state => state.ui.modified)
|
||||||
|
|
||||||
const boolOp = (code) => {
|
const boolOp = (code) => {
|
||||||
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
|
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
|
||||||
const [m1, m2] = sc.selected
|
const [m1, m2] = sc.selected
|
||||||
sc.boolOp(m1, m2, code)
|
|
||||||
|
const mesh = sc.boolOp(m1, m2, code)
|
||||||
|
|
||||||
|
sc.obj3d.add(mesh)
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'set-entry-visibility', obj: {
|
||||||
|
[m1.name]: false,
|
||||||
|
[m2.name]: false,
|
||||||
|
[mesh.name]: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'rx-boolean', mesh, deps: [m1.name, m2.name]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
sc.render()
|
sc.render()
|
||||||
forceUpdate()
|
forceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
const addSketch = () => {
|
const addSketch = async () => {
|
||||||
sc.addSketch()
|
const sketch = await sc.addSketch()
|
||||||
|
|
||||||
|
dispatch({ type: 'rx-sketch', obj: sketch })
|
||||||
|
|
||||||
|
sketch.activate()
|
||||||
|
|
||||||
|
sc.render()
|
||||||
|
|
||||||
dispatch({ type: 'set-dialog', action: 'sketch' })
|
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||||
|
|
||||||
forceUpdate()
|
forceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmDiscard = () => !modified ? true : confirm('Discard changes? All changes will be lost.')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onBeforeUnload = (e) => {
|
||||||
|
if (modified ||
|
||||||
|
(sc.activeSketch &&
|
||||||
|
(
|
||||||
|
sc.activeSketch.hasChanged
|
||||||
|
|| sc.activeSketch.idOnActivate != id
|
||||||
|
|| sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = `There are unsaved changes. Are you sure you want to leave?`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('beforeunload', onBeforeUnload)
|
||||||
|
return () => window.removeEventListener('beforeunload', onBeforeUnload)
|
||||||
|
}, [modified])
|
||||||
|
|
||||||
useEffect(() => { // hacky way to handle mounting and unmounting mouse listeners for feature mode
|
useEffect(() => { // hacky way to handle mounting and unmounting mouse listeners for feature mode
|
||||||
if (!activeSketchId) {
|
if (!sketchActive) {
|
||||||
sc.canvas.addEventListener('pointermove', sc.onHover)
|
sc.canvas.addEventListener('pointermove', sc.onHover)
|
||||||
sc.canvas.addEventListener('pointerdown', sc.onPick)
|
sc.canvas.addEventListener('pointerdown', sc.onPick)
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -48,11 +93,10 @@ export const NavBar = () => {
|
||||||
sc.canvas.removeEventListener('pointerdown', sc.onPick)
|
sc.canvas.removeEventListener('pointerdown', sc.onPick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeSketchId])
|
}, [sketchActive])
|
||||||
|
|
||||||
const sketchModeButtons = [
|
const sketchModeButtons = [
|
||||||
[Icon.Extrude, () => {
|
[Icon.Extrude, () => {
|
||||||
dispatch({ type: 'finish-sketch' })
|
|
||||||
dispatch({ type: 'set-dialog', action: 'extrude', target: sc.activeSketch })
|
dispatch({ type: 'set-dialog', action: 'extrude', target: sc.activeSketch })
|
||||||
|
|
||||||
}, 'Extrude [e]'],
|
}, 'Extrude [e]'],
|
||||||
|
@ -63,32 +107,43 @@ export const NavBar = () => {
|
||||||
[Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical [v]'],
|
[Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical [v]'],
|
||||||
[Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal [h]'],
|
[Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal [h]'],
|
||||||
[Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'],
|
[Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'],
|
||||||
[Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'],
|
[MdSave,
|
||||||
|
async () => {
|
||||||
|
if(await verifyPermission(fileHandle) === false) return
|
||||||
|
sc.refreshNode(sc.activeSketch.obj3d.name, treeEntries)
|
||||||
|
sc.activeSketch.clearSelection()
|
||||||
|
saveFile(fileHandle, JSON.stringify([id, sc.sid, sc.mid, treeEntries]), dispatch)
|
||||||
|
sc.render()
|
||||||
|
sc.activeSketch.setClean()
|
||||||
|
}
|
||||||
|
, 'Save']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
const partModeButtons = [
|
const partModeButtons = [
|
||||||
[FaEdit, addSketch, 'Sketch [s]'],
|
[FaEdit, addSketch, 'Sketch [s]'],
|
||||||
[Icon.Extrude, () => {
|
[Icon.Extrude, () => {
|
||||||
dispatch({ type: 'set-dialog', action: 'extrude', target: treeEntriesById[sc.selected[0].name] })
|
dispatch({ type: 'set-dialog', action: 'extrude', target: treeEntries.byId[sc.selected[0].name] })
|
||||||
}, 'Extrude [e]'],
|
}, 'Extrude'],
|
||||||
|
|
||||||
[Icon.Union, () => boolOp('u'), 'Union'],
|
[Icon.Union, () => boolOp('u'), 'Union'],
|
||||||
[Icon.Subtract, () => boolOp('s'), 'Subtract'],
|
[Icon.Subtract, () => boolOp('s'), 'Subtract'],
|
||||||
[Icon.Intersect, () => boolOp('i'), 'Intersect'],
|
[Icon.Intersect, () => boolOp('i'), 'Intersect'],
|
||||||
[MdInsertDriveFile, () => {
|
[MdInsertDriveFile, () => {
|
||||||
|
if (!confirmDiscard()) return
|
||||||
sc.newPart()
|
sc.newPart()
|
||||||
dispatch({ type: 'new-part' })
|
dispatch({ type: 'new-part' })
|
||||||
sc.render()
|
sc.render()
|
||||||
}, 'New [ctrl+n]'],
|
}, 'New'],
|
||||||
[MdSave,
|
[MdSave,
|
||||||
() => {
|
() => {
|
||||||
saveFile(fileHandle, sc.saveScene(), dispatch)
|
saveFile(fileHandle, JSON.stringify([id, sc.sid, sc.mid, treeEntries]), dispatch)
|
||||||
}
|
}
|
||||||
, 'Save [ctrl+s]'],
|
, 'Save'],
|
||||||
[MdFolder, () => {
|
[MdFolder, () => {
|
||||||
|
if (!confirmDiscard()) return
|
||||||
openFile(dispatch).then(
|
openFile(dispatch).then(
|
||||||
()=>sc.render()
|
sc.render
|
||||||
)
|
)
|
||||||
}, 'Open'],
|
}, 'Open'],
|
||||||
[Icon.Stl, () => {
|
[Icon.Stl, () => {
|
||||||
|
@ -106,7 +161,7 @@ export const NavBar = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className='w-auto h-full flex-none'>
|
<div className='w-auto h-full flex-none'>
|
||||||
{
|
{
|
||||||
activeSketchId ?
|
sketchActive ?
|
||||||
sketchModeButtons.map(([Icon, fcn, txt, shortcut], idx) => (
|
sketchModeButtons.map(([Icon, fcn, txt, shortcut], idx) => (
|
||||||
<Icon className="btn w-auto h-full p-3.5" tooltip={txt}
|
<Icon className="btn w-auto h-full p-3.5" tooltip={txt}
|
||||||
onClick={fcn} key={idx}
|
onClick={fcn} key={idx}
|
||||||
|
|
|
@ -10,7 +10,6 @@ const defaultState = {
|
||||||
tree: {},
|
tree: {},
|
||||||
order: {},
|
order: {},
|
||||||
visible: {},
|
visible: {},
|
||||||
activeSketchId: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache
|
let cache
|
||||||
|
@ -33,21 +32,19 @@ export function treeEntries(state = defaultState, action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'set-active-sketch':
|
case 'set-active-sketch':
|
||||||
cache = JSON.stringify(state.byId[action.activeSketchId])
|
cache = JSON.stringify(action.sketch)
|
||||||
return update(state, {
|
return update(state, {
|
||||||
visible: { [action.activeSketchId]: { $set: true } },
|
visible: { [action.sketch.obj3d.name]: { $set: true } },
|
||||||
activeSketchId: { $set: action.activeSketchId },
|
|
||||||
})
|
})
|
||||||
case 'finish-sketch':
|
case 'finish-sketch':
|
||||||
return update(state, {
|
return update(state, {
|
||||||
activeSketchId: { $set: "" },
|
visible: { [sc.activeSketch.obj3d.name]: { $set: false } },
|
||||||
visible: { [state.activeSketchId]: { $set: false } },
|
|
||||||
})
|
})
|
||||||
case 'restore-sketch':
|
case 'restore-sketch':
|
||||||
|
|
||||||
const sketch = sc.loadSketch(cache)
|
const sketch = sc.loadSketch(cache)
|
||||||
|
|
||||||
const deletedObj = sc.obj3d.children.splice(state.order[state.activeSketchId] + 1, 1,
|
const deletedObj = sc.obj3d.children.splice(state.order[sc.activeSketch.obj3d.name] + 1, 1,
|
||||||
sketch.obj3d
|
sketch.obj3d
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
|
@ -59,9 +56,7 @@ export function treeEntries(state = defaultState, action) {
|
||||||
sc.activeSketch = sketch
|
sc.activeSketch = sketch
|
||||||
|
|
||||||
return update(state, {
|
return update(state, {
|
||||||
activeSketchId: { $set: "" },
|
byId: { [sc.activeSketch.obj3d.name]: { $set: sketch } },
|
||||||
byId: { [state.activeSketchId]: { $set: sketch } },
|
|
||||||
visible: { [state.activeSketchId]: { $set: false } },
|
|
||||||
})
|
})
|
||||||
case 'rx-extrusion':
|
case 'rx-extrusion':
|
||||||
|
|
||||||
|
@ -108,7 +103,18 @@ export function treeEntries(state = defaultState, action) {
|
||||||
|
|
||||||
export function ui(state = { dialog: {}, filePane: false }, action) {
|
export function ui(state = { dialog: {}, filePane: false }, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'set-active-sketch':
|
||||||
|
return update(state, {
|
||||||
|
sketchActive: { $set: true },
|
||||||
|
})
|
||||||
|
case 'rx-sketch':
|
||||||
|
return update(state, {
|
||||||
|
sketchActive: { $set: true },
|
||||||
|
})
|
||||||
|
case 'finish-sketch':
|
||||||
|
return update(state, {
|
||||||
|
sketchActive: { $set: false },
|
||||||
|
})
|
||||||
case 'set-dialog':
|
case 'set-dialog':
|
||||||
return update(state, {
|
return update(state, {
|
||||||
dialog: { $set: { target: action.target, action: action.action } },
|
dialog: { $set: { target: action.target, action: action.action } },
|
||||||
|
@ -127,6 +133,18 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
|
||||||
fileHandle: { $set: null },
|
fileHandle: { $set: null },
|
||||||
modified: { $set: false },
|
modified: { $set: false },
|
||||||
})
|
})
|
||||||
|
case 'set-modified':
|
||||||
|
return update(state, {
|
||||||
|
modified: { $set: action.status },
|
||||||
|
})
|
||||||
|
case 'delete-node':
|
||||||
|
return update(state, {
|
||||||
|
modified: { $set: true },
|
||||||
|
})
|
||||||
|
case 'rx-extrusion':
|
||||||
|
return update(state, {
|
||||||
|
modified: { $set: true },
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,12 @@ import { FaCube, FaEdit } from 'react-icons/fa'
|
||||||
|
|
||||||
export const Tree = () => {
|
export const Tree = () => {
|
||||||
const treeEntries = useSelector(state => state.treeEntries)
|
const treeEntries = useSelector(state => state.treeEntries)
|
||||||
const ref = useRef()
|
const fileHandle = useSelector(state => state.ui.fileHandle)
|
||||||
|
|
||||||
return <div className='sideNav flex flex-col bg-gray-800'>
|
return <div className='sideNav flex flex-col bg-gray-800'>
|
||||||
<input className='w-16 text-gray-50 h-7 mx-1 border-0 focus:outline-none bg-transparent' type="text" defaultValue="untitled" step="0.1" ref={ref} />
|
<div className='w-16 text-gray-50 h-7 mx-1 border-0 focus:outline-none bg-transparent'>
|
||||||
|
{fileHandle ? fileHandle.name.replace(/\.[^/.]+$/, "") : 'untitled'}
|
||||||
|
</div>
|
||||||
{treeEntries.allIds.map((entId, idx) => (
|
{treeEntries.allIds.map((entId, idx) => (
|
||||||
<TreeEntry key={idx} entId={entId} />
|
<TreeEntry key={idx} entId={entId} />
|
||||||
))}
|
))}
|
||||||
|
@ -55,7 +57,11 @@ const TreeEntry = ({ entId }) => {
|
||||||
dispatch({ type: 'finish-sketch' })
|
dispatch({ type: 'finish-sketch' })
|
||||||
sc.activeSketch.deactivate()
|
sc.activeSketch.deactivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sketch.activate()
|
sketch.activate()
|
||||||
|
dispatch({ type: 'set-active-sketch', sketch })
|
||||||
|
|
||||||
sc.clearSelection()
|
sc.clearSelection()
|
||||||
sc.activeSketch = sketch;
|
sc.activeSketch = sketch;
|
||||||
dispatch({ type: 'set-dialog', action: 'sketch' })
|
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||||
|
@ -120,14 +126,6 @@ const TreeEntry = ({ entId }) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <MdRefresh className='btn-green h-full w-auto p-1.5'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
sc.refreshNode(entId)
|
|
||||||
sc.render()
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
visible ?
|
visible ?
|
||||||
|
|
1
todo.txt
1
todo.txt
|
@ -36,6 +36,7 @@ auto update extrude // done
|
||||||
extrude edit dialog // done
|
extrude edit dialog // done
|
||||||
file save, stl export// done
|
file save, stl export// done
|
||||||
|
|
||||||
|
-unable cancel out of new sketches //fixed seemingly
|
||||||
|
|
||||||
-sometimes unable to hit return and change dimensionk
|
-sometimes unable to hit return and change dimensionk
|
||||||
-unable to delete arc
|
-unable to delete arc
|
||||||
|
|
Loading…
Reference in New Issue