diff --git a/dist/favicon.ico b/dist/favicon.ico index 08af657..30805fe 100644 Binary files a/dist/favicon.ico and b/dist/favicon.ico differ diff --git a/dist/icon-192.png b/dist/icon-192.png index d4e4179..017a82d 100644 Binary files a/dist/icon-192.png and b/dist/icon-192.png differ diff --git a/dist/icon-512.png b/dist/icon-512.png index fb8d574..efefec7 100644 Binary files a/dist/icon-512.png and b/dist/icon-512.png differ diff --git a/icon/icon-16.png b/icon/icon-16.png index 0582446..fb1133a 100644 Binary files a/icon/icon-16.png and b/icon/icon-16.png differ diff --git a/icon/icon-24.png b/icon/icon-24.png index 487ff5c..21f58e2 100644 Binary files a/icon/icon-24.png and b/icon/icon-24.png differ diff --git a/icon/icon-32.png b/icon/icon-32.png index 3dd7aae..128e66b 100644 Binary files a/icon/icon-32.png and b/icon/icon-32.png differ diff --git a/icon/icon-64.png b/icon/icon-64.png index b70bf36..ba62b6a 100644 Binary files a/icon/icon-64.png and b/icon/icon-64.png differ diff --git a/icon/icon-text.svg b/icon/icon-text.svg new file mode 100644 index 0000000..37324f9 --- /dev/null +++ b/icon/icon-text.svg @@ -0,0 +1,98 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + three.cad + + + diff --git a/icon/icon2.svg b/icon/icon2.svg new file mode 100644 index 0000000..d47a08f --- /dev/null +++ b/icon/icon2.svg @@ -0,0 +1,83 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/icon/icon_text.svg b/icon/icon_text.svg new file mode 100644 index 0000000..a91a5ad --- /dev/null +++ b/icon/icon_text.svg @@ -0,0 +1,124 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon/svg-to-favicon b/icon/svg-to-favicon index ed21669..fea83d8 100755 --- a/icon/svg-to-favicon +++ b/icon/svg-to-favicon @@ -1,19 +1,19 @@ #!/usr/bin/env bash -svg=icon +svg=icon2 size=(16 24 32 64) out="" for i in ${size[@]}; do - inkscape --export-filename="./$svg-$i.png" $svg.svg -w $i -h $i - out+="$svg-$i.png " + inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i + out+="icon-$i.png " done size=(192 512) for i in ${size[@]}; do - inkscape --export-filename="./$svg-$i.png" $svg.svg -w $i -h $i + inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i done convert $out favicon.ico -mv favicon.ico icon-192.png icon-512.png ../dist \ No newline at end of file +mv favicon.ico icon-192.png icon-512.png ../dist diff --git a/icon/svgr_raw/icon_text.svg b/icon/svgr_raw/icon_text.svg new file mode 100644 index 0000000..a91a5ad --- /dev/null +++ b/icon/svgr_raw/icon_text.svg @@ -0,0 +1,124 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon/svgr_raw/logo.svg b/icon/svgr_raw/logo.svg new file mode 100644 index 0000000..d47a08f --- /dev/null +++ b/icon/svgr_raw/logo.svg @@ -0,0 +1,83 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/lib/stats.module.js b/lib/stats.module.js index 79fc7ef..6841caa 100644 --- a/lib/stats.module.js +++ b/lib/stats.module.js @@ -165,4 +165,4 @@ Stats.Panel = function (name, fg, bg) { }; -export default Stats; +export default Stats; \ No newline at end of file diff --git a/src/Scene.js b/src/Scene.js index d905c23..6ec6dda 100644 --- a/src/Scene.js +++ b/src/Scene.js @@ -10,9 +10,15 @@ import { AxesHelper } from './axes' import { TrackballControls } from '../lib/trackball' import CSG from "../lib/three-csg" import { STLExporter } from '../lib/stl' -import Stats from '../lib/stats.module.js'; +let stats +if (process.env.NODE_ENV !== 'production') { + const { default: d } = require('../lib/stats.module.js') + stats = new d(); + document.getElementById('stats').appendChild(stats.dom); +} + window.loader = new THREE.ObjectLoader(); window.STLexp = new STLExporter(); @@ -145,10 +151,13 @@ export class Scene { controls.addEventListener('start', this.render); window.addEventListener('resize', this.render); + if (process.env.NODE_ENV !== 'production') { + this.stats = stats + this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom + document.getElementById('stats').appendChild(this.stats.dom); + } + - this.stats = new Stats(); - this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom - document.getElementById('stats').appendChild(this.stats.dom); this.hovered = []; @@ -331,7 +340,11 @@ export class Scene { let idx, x, y, ele, pos, dims, matrix; function render() { - this.stats.begin(); + if (process.env.NODE_ENV !== 'production') { + this.stats.begin(); + } + + if (this.resizeCanvas(this.renderer)) { const canvas = this.renderer.domElement; this.camera.left = -canvas.clientWidth / canvas.clientHeight; @@ -366,7 +379,9 @@ function render() { } - this.stats.end(); + if (process.env.NODE_ENV !== 'production') { + this.stats.end(); + } } @@ -374,7 +389,7 @@ function addSketch() { let sketch; - if (this.selected.length == 3 && this.selected.every(e=>e.userData.type == 'selpoint')) { + if (this.selected.length == 3 && this.selected.every(e => e.userData.type == 'selpoint')) { sketch = new Sketch(this) this.obj3d.add(sketch.obj3d) sketch.align( @@ -400,10 +415,4 @@ function addSketch() { return sketch } -window.sc = new Scene(store) -// sc.loadState() - - - -// sc.camera.layers.enable(1) -// rc.layers.set(1) \ No newline at end of file +window.sc = new Scene(store) \ No newline at end of file diff --git a/src/Sketch.js b/src/Sketch.js index 9b3780e..6c4fc38 100644 --- a/src/Sketch.js +++ b/src/Sketch.js @@ -187,7 +187,6 @@ class Sketch { } deactivate() { - console.log('deactivate') window.removeEventListener('keydown', this.onKeyPress) this.canvas.removeEventListener('pointerdown', this.onPick) this.canvas.removeEventListener('pointermove', this.onHover) diff --git a/src/react/app.css b/src/react/app.css index 807aa48..53c7509 100644 --- a/src/react/app.css +++ b/src/react/app.css @@ -53,50 +53,19 @@ body { .btn { cursor: pointer; @apply fill-current - bg-transparent text-gray-200 - hover:bg-gray-500 hover:text-gray-200; + bg-transparent hover:bg-gray-600; } -.active-btn { - cursor: pointer; - @apply fill-current - bg-green-400 text-gray-200 -} - - .btn-green { cursor: pointer; @apply fill-current - bg-transparent text-gray-200 - hover:bg-transparent hover:text-green-400; + bg-transparent text-gray-200 + hover:text-green-400; } -.tooltip { - position: fixed; - display: block; - background-color: black; - color: #fff; - text-align: center; - border-radius: 4px; - padding: 4px; - visibility: hidden; - border: solid 1px white; - -} - -.arrow { - position: absolute; - bottom: 100%; - left: 50%; - margin-left: -6px; - border: solid 6px transparent; - border-bottom-color: white; - border-top: none; -} - input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; @@ -112,5 +81,17 @@ input[type=number] { text-align:right; } -/* input:focus[type=number] { */ -/* } */ +.arrow { + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -6px; + border: solid 6px transparent; + border-bottom-color: #3F3F46; + border-top: none; + +} + +.drop-down-top { + top: calc(var(--topNavH) + 6px); +} diff --git a/src/react/app.jsx b/src/react/app.jsx index 09b0cd3..5212cb9 100644 --- a/src/react/app.jsx +++ b/src/react/app.jsx @@ -4,7 +4,6 @@ import React from 'react' import { createStore, applyMiddleware } from 'redux' import { Provider } from 'react-redux' import { reducer } from './reducer' -// import logger from 'redux-logger' import { Tree } from './tree' import { NavBar } from './navBar' diff --git a/src/react/dialog.jsx b/src/react/dialog.jsx index 7ff8c26..42dcc8d 100644 --- a/src/react/dialog.jsx +++ b/src/react/dialog.jsx @@ -35,9 +35,15 @@ export const Dialog = () => { sc.render() } + const extrudeCancel = () => { + 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" }) + } + } + const extrudeEdit = () => { - - dialog.target.userData.featureInfo[1] = ref.current.value sc.refreshNode(dialog.target.name, treeEntries) @@ -48,83 +54,83 @@ export const Dialog = () => { sc.render() } + const extrudeEditCancel = () => dispatch({ type: "clear-dialog" }) + + const sketchDone = () => { + if (sc.activeSketch.hasChanged + || sc.activeSketch.idOnActivate != id + || sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id + ) { + sc.refreshNode(sc.activeSketch.obj3d.name, treeEntries) + + dispatch({ type: 'set-modified', status: true }) + } + + dispatch({ type: 'finish-sketch' }) + + sc.activeSketch.deactivate() + sc.render() + dispatch({ type: "clear-dialog" }) + } + + const sketchCancel = () => { + if (sc.activeSketch.hasChanged + || sc.activeSketch.idOnActivate != id + || sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id + ) { + if (sc.newSketch) { + dispatch({ type: 'delete-node', id: sc.activeSketch.obj3d.name }) + sc.sid -= 1 + } else { + dispatch({ type: "restore-sketch" }) + } + } + + dispatch({ type: 'finish-sketch' }) + + sc.activeSketch.deactivate() + sc.render() + dispatch({ type: "clear-dialog" }) + } switch (dialog.action) { case 'extrude': return <> - - + ref.current.value *= -1} /> - { - 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': return <> - - + ref.current.value *= -1} /> - dispatch({ type: "clear-dialog" })} + case 'sketch': return <> { - if (sc.activeSketch.hasChanged - || sc.activeSketch.idOnActivate != id - || sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id - ) { - sc.refreshNode(sc.activeSketch.obj3d.name, treeEntries) - - dispatch({ type: 'set-modified', status: true }) - } - - dispatch({ type: 'finish-sketch' }) - - sc.activeSketch.deactivate() - sc.render() - dispatch({ type: "clear-dialog" }) - }} + className="btn w-auto h-full p-3.5 text-green-500" + onClick={sketchDone} /> - { - if (sc.activeSketch.hasChanged - || sc.activeSketch.idOnActivate != id - || sc.activeSketch.c_idOnActivate != sc.activeSketch.c_id - ) { - if (sc.newSketch) { - dispatch({ type: 'delete-node', id: sc.activeSketch.obj3d.name }) - sc.sid -= 1 - } else { - dispatch({ type: "restore-sketch" }) - } - } - - dispatch({ type: 'finish-sketch' }) - - sc.activeSketch.deactivate() - sc.render() - dispatch({ type: "clear-dialog" }) - }} + default: diff --git a/src/react/dropDown.jsx b/src/react/dropDown.jsx new file mode 100644 index 0000000..e7409ea --- /dev/null +++ b/src/react/dropDown.jsx @@ -0,0 +1,75 @@ + +import React, { useState } from 'react'; + +import { useDispatch, useSelector } from 'react-redux' + + +export const DropDown = () => { + const arr = [ + ['https://raw.githubusercontent.com/twpride/threeCAD/master/example_parts/test2.json', 'test2'], + ['https://raw.githubusercontent.com/twpride/threeCAD/master/example_parts/test2.json', 'test2'], + ['https://raw.githubusercontent.com/twpride/threeCAD/master/example_parts/test2.json', 'test2'], + ['https://raw.githubusercontent.com/twpride/threeCAD/master/example_parts/test2.json', 'test2'], + ] + + const dispatch = useDispatch() + const [open, setOpen] = useState(false) + + const handleOutsideClick = (ev) => { + /* + this handles inside click as well due to bubbling, + sets the open/close state of drop down + */ + setOpen(state => !state) // handle click on button & dropdown, always a toggle + + document.addEventListener( // handles click outside dropdown & button + 'pointerdown', + (e) => { + !e.path.includes(ev.target.parentNode) && setOpen(false) + } + , + { capture: true, once: true } // capture phase to allow for stopPropogation on others + ) + + } + + const handleInsideClick = (e) => { + // handles click inside dropdown, business logic here + const idx = Array.prototype.indexOf.call(e.target.parentNode.children, e.target) + if (idx !== -1) { + console.log(idx) + fetch(arr[idx][0]) + .then(res => res.text()) + .then(text => { + dispatch({ type: 'restore-state', state: sc.loadState(text) }) + fileHandle.name = 'something' + console.log(fileHandle.name) + sc.render() + }) + } + } + const fileHandle = useSelector(state => state.ui.fileHandle) + return
+
+ Demo Parts +
+ { + open && +
+ {arr.map(([url, name], idx) => ( +
+ {name} +
+ ))} +
+
+ } +
+} \ No newline at end of file diff --git a/src/react/fileHelpers.js b/src/react/fileHelpers.js index e0fa7ac..32ece26 100644 --- a/src/react/fileHelpers.js +++ b/src/react/fileHelpers.js @@ -110,8 +110,6 @@ export async function openFile(dispatch) { dispatch({ type: 'restore-state', state: sc.loadState(text) }) dispatch({ type: 'set-file-handle', fileHandle }) - // app.setFocus(true); - } catch (ex) { const msg = `An error occured reading ${fileHandle}`; console.error(msg, ex); diff --git a/src/react/icons.jsx b/src/react/icons.jsx index 988281e..dce9f84 100644 --- a/src/react/icons.jsx +++ b/src/react/icons.jsx @@ -289,6 +289,40 @@ function Horizontal(props) { ); } +function Icon_text(props) { + return ( + + + + + + + + + + + + ); +} + function Intersect(props) { return ( + + + + + ); +} + function Stl(props) { return ( ); } -export { Arc, Coincident, Dimension, Extrude, Extrude_master, Flip, Horizontal, Intersect, Intersect_thin, Line, Stl, Subtract, Tangent, Union, Union_thin, Vertical }; \ No newline at end of file +export { Arc, Coincident, Dimension, Extrude, Extrude_master, Flip, Horizontal, Icon_text, Intersect, Intersect_thin, Line, Logo, Stl, Subtract, Tangent, Union, Union_thin, Vertical }; \ No newline at end of file diff --git a/src/react/navBar.jsx b/src/react/navBar.jsx index b1a0e3a..25f7ce2 100644 --- a/src/react/navBar.jsx +++ b/src/react/navBar.jsx @@ -1,6 +1,6 @@ -import React, { useEffect, useReducer } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux' @@ -9,6 +9,7 @@ import { MdSave, MdFolder, MdInsertDriveFile } from 'react-icons/md' import * as Icon from "./icons"; import { Dialog } from './dialog' +import { DropDown } from './dropDown' import { STLExport, saveFile, openFile, verifyPermission } from './fileHelpers' export const NavBar = () => { @@ -103,14 +104,14 @@ export const NavBar = () => { [Icon.Extrude, () => { dispatch({ type: 'set-dialog', action: 'extrude', target: sc.activeSketch }) - }, 'Extrude [e]'], - [Icon.Dimension, () => sc.activeSketch.command('d'), 'Dimension [D]'], - [Icon.Line, () => sc.activeSketch.command('l'), 'Line [L]'], - [Icon.Arc, () => sc.activeSketch.command('a'), 'Arc [A]'], - [Icon.Coincident, () => sc.activeSketch.command('c'), 'Coincident [C]'], - [Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical [V]'], - [Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal [H]'], - [Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [T]'], + }, 'Extrude'], + [Icon.Dimension, () => sc.activeSketch.command('d'), 'Dimension (D)'], + [Icon.Line, () => sc.activeSketch.command('l'), 'Line (L)'], + [Icon.Arc, () => sc.activeSketch.command('a'), 'Arc (A)'], + [Icon.Coincident, () => sc.activeSketch.command('c'), 'Coincident (C)'], + [Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical (V)'], + [Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal (H)'], + [Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent (T)'], [MdSave, async () => { if (await verifyPermission(fileHandle) === false) return @@ -166,28 +167,35 @@ export const NavBar = () => { const [_, forceUpdate] = useReducer(x => x + 1, 0); - return
+ return
-
- + {/*
*/} +
+
+ + three.cad +
+
+ +
-
- { - sketchActive ? - sketchModeButtons.map(([Icon, fcn, txt, shortcut], idx) => ( - - )) - : - partModeButtons.map(([Icon, fcn, txt, shortcut], idx) => ( - - )) +
+ {sketchActive ? + sketchModeButtons.map(([Icon, fcn, txt], idx) => ( + + )) + : + partModeButtons.map(([Icon, fcn, txt], idx) => ( + + )) }
-
+
+ @@ -200,3 +208,6 @@ export const NavBar = () => { } + + + diff --git a/src/react/toolTip copy.jsx b/src/react/toolTip copy.jsx new file mode 100644 index 0000000..1425a22 --- /dev/null +++ b/src/react/toolTip copy.jsx @@ -0,0 +1,78 @@ +import React, { useEffect, useRef, useState } from 'react'; + +export const ToolTip = () => { + /** + * Fires when new element is mouseovered, checks if it has a tooltip attribute + * If it does, updates and unhides tooltip element after a preset timeout. + * The timout is reset if user moves off of the tooltipped element + * + * Unfortunately, new mouseover fires for svg children, which clears the + * tooltip state. We add hacky lines labelled svg workaround to bubbleup / ignore + * svg children mouseovers. We use prevTooltip ref check if new svg + * child mouseover is novel. If it's not, we ignore the event + */ + + const [text, setText] = useState(null) + + const ref = useRef() + + const activated = useRef(false) + const timeout = useRef(null) + + const prevTooltip = useRef(null) // svg workaround + + useEffect(() => { + + const svgChildren = ['path', 'g', 'rect', 'circle']; // svg workaround + + document.addEventListener('mouseover', (e) => { + let node = e.target; + + while (svgChildren.includes(node.nodeName)) { // svg workaround + node = node.parentElement // svg workaround + } // svg workaround + + const tooltip = node.getAttribute("tooltip") + + if (tooltip == prevTooltip.current) return // svg workaround + prevTooltip.current = tooltip // svg workaround + + clearTimeout(timeout.current) + if (tooltip) { + let { left, top, width, height } = node.getBoundingClientRect() + left = left + width / 2 - getTextWidth(tooltip) / 2 - 4 // 4 is padding + top = top + height + 6 // 6 is arrow height/width + setText(tooltip) + if (activated.current) { + ref.current.setAttribute('style', `left:${left}px; top:${top}px; visibility:visible`) + } else { + timeout.current = setTimeout(() => { + ref.current.setAttribute('style', `left:${left}px; top:${top}px; visibility:visible`) + activated.current = true + }, 1000); + } + } else { + ref.current.setAttribute('style', `visibility:hidden`) + activated.current = false + } + }) + }, []) + + + return
+ {text} +
+
+ +} + + +function getTextWidth(text, font = "16px sans-serif") { + // https://stackoverflow.com/a/21015393 + // re-use canvas object for better performance + let canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas")); + let context = canvas.getContext("2d"); + context.font = font; + let metrics = context.measureText(text); + return metrics.width; +} diff --git a/src/react/toolTip.jsx b/src/react/toolTip.jsx index 5620d8f..a332765 100644 --- a/src/react/toolTip.jsx +++ b/src/react/toolTip.jsx @@ -19,7 +19,7 @@ export const ToolTip = () => { const activated = useRef(false) const timeout = useRef(null) - const prevTooltip = useRef(null) // svg workaround + // const prevTooltip = useRef(null) // svg workaround useEffect(() => { @@ -34,8 +34,9 @@ export const ToolTip = () => { const tooltip = node.getAttribute("tooltip") - if (tooltip == prevTooltip.current) return // svg workaround - prevTooltip.current = tooltip // svg workaround + // console.log(tooltip, prevTooltip.current) + // if (tooltip == prevTooltip.current) return // svg workaround + // prevTooltip.current = tooltip // svg workaround clearTimeout(timeout.current) if (tooltip) { @@ -49,7 +50,7 @@ export const ToolTip = () => { timeout.current = setTimeout(() => { ref.current.setAttribute('style', `left:${left}px; top:${top}px; visibility:visible`) activated.current = true - }, 1000); + }, 700); } } else { ref.current.setAttribute('style', `visibility:hidden`) @@ -59,7 +60,7 @@ export const ToolTip = () => { }, []) - return
+ return
{text}
diff --git a/src/react/tree.jsx b/src/react/tree.jsx index fc83c29..e29ddfa 100644 --- a/src/react/tree.jsx +++ b/src/react/tree.jsx @@ -50,7 +50,7 @@ const TreeEntry = ({ entId }) => { const [_, forceUpdate] = useReducer(x => x + 1, 0); const [mouseOn, setMouseOn] = useState(false) - return
{ if (obj3d.userData.type == 'sketch') { if (sc.activeSketch) { @@ -71,7 +71,6 @@ const TreeEntry = ({ entId }) => { } }} - onPointerEnter={() => { if (mouseOn) return setMouseOn(true) @@ -112,9 +111,13 @@ const TreeEntry = ({ entId }) => { } sc.render() }} + + tooltip= {obj3d.name[0] !='(' && "double click to edit"} + // tooltip= {obj3d.userData.name} + > -
+
{entId}
diff --git a/tailwind.config.js b/tailwind.config.js index e4a6284..5e98c0e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,6 +13,7 @@ module.exports = { current: 'currentColor', gray: colors.trueGray, green: colors.emerald, + red: colors.red, } }, variants: {