Merge pull request #428 from Irev-Dev/kurt/411-demo-branch

initial jscad integration
This commit was merged in pull request #428.
This commit is contained in:
Kurt Hutten
2021-08-01 19:35:56 +10:00
committed by GitHub
10 changed files with 843 additions and 19 deletions

View File

@@ -2,6 +2,7 @@
"cSpell.words": [
"Hutten",
"cadquery",
"jscad",
"openscad",
"sendmail"
]

View File

@@ -41,6 +41,7 @@ model User {
enum CadPackage {
openscad
cadquery
// jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
}
model Project {

View File

@@ -0,0 +1,625 @@
(function(f) {
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = f()
} else if (typeof define === "function" && define.amd) {
define([], f)
} else {
var g;
if (typeof window !== "undefined") {
g = window
} else if (typeof global !== "undefined") {
g = global
} else if (typeof self !== "undefined") {
g = self
} else {
g = this
}
g.jscadWorker = f()
}
})(function() {
// multi purpose module
const setPoints = (points, p, i)=>{
points[i++] = p[0]
points[i++] = p[1]
points[i++] = p[2] || 0
}
function CSG2Vertices(csg){
let idx = 0
let vLen = 0, iLen = 0
for (let poly of csg.polygons){
let len = poly.vertices.length
vLen += len *3
iLen += 3 * (len-2)
}
const vertices = new Float32Array(vLen)
const indices = vLen > 65535 ? new Uint32Array(iLen) : new Uint16Array(iLen)
let vertOffset = 0
let indOffset = 0
let posOffset = 0
let first = 0
for (let poly of csg.polygons){
let arr = poly.vertices
let len = arr.length
first = posOffset
vertices.set(arr[0], vertOffset)
vertOffset +=3
vertices.set(arr[1], vertOffset)
vertOffset +=3
posOffset +=2
for(let i=2; i<len; i++){
vertices.set(arr[i], vertOffset)
indices[indOffset++] = first
indices[indOffset++] = first + i -1
indices[indOffset++] = first + i
vertOffset += 3
posOffset += 1
}
}
return {vertices, indices, type:'mesh'}
}
function CSG2LineVertices(csg){
let vLen = csg.points.length * 3
if(csg.isClosed) vLen += 3
var vertices = new Float32Array(vLen)
csg.points.forEach((p,idx)=>setPoints(vertices, p, idx * 3 ))
if(csg.isClosed){
setPoints(vertices, csg.points[0], vertices.length - 3 )
}
return {vertices, type:'line'}
}
function CSG2LineSegmentsVertices(csg){
let vLen = csg.sides.length * 6
var vertices = new Float32Array(vLen)
csg.sides.forEach((side,idx)=>{
let i = idx * 6
setPoints(vertices, side[0], i)
setPoints(vertices, side[1], i+3)
})
return {vertices, type:'lines'}
}
function CSGCached(func, data, cacheKey, transferable){
cacheKey = cacheKey || data
let geo = CSGToBuffers.cache.get(cacheKey)
if(geo) return geo
geo = func(data)
// fill transferable array for postMessage optimization
if(transferable){
const {vertices, indices} = geo
transferable.push(vertices)
if(indices) transferable.push(indices)
}
CSGToBuffers.cache.set(cacheKey, geo)
return geo
}
function CSGToBuffers(csg, transferable){
let obj
if(csg.polygons) obj = CSGCached(CSG2Vertices,csg,csg.polygons, transferable)
if(csg.sides && !csg.points) obj = CSGCached(CSG2LineSegmentsVertices,csg,csg.sides, transferable)
if(csg.points) obj = CSGCached(CSG2LineVertices,csg,csg.points, transferable)
return obj
}
CSGToBuffers.clearCache = ()=>{CSGToBuffers.cache = new WeakMap()}
CSGToBuffers.clearCache()
let workerBaseURI
function require(url){
url = require.alias[url] || url
if(url[0] != '/' && url.substr(0,2) != './' && url.substr(0,4) != 'http') url = 'https://unpkg.com/'+url
let exports=require.cache[url]; //get from cache
if (!exports) { //not cached
let module = requireModule(url)
require.cache[url] = exports = module.exports; //cache obj exported by module
}
return exports; //require returns object exported by module
}
function requireFile(url){
try{
let X=new XMLHttpRequest();
X.open("GET", new URL(url,workerBaseURI), 0); // sync
X.send();
if (X.status && X.status !== 200) throw new Error(X.statusText);
return X.responseText;
}catch(e){
console.log('problem loading url ',url,'base',workerBaseURI,' error:',e.message)
throw e
}
}
function requireModule(url, source){
try {
const exports={};
if(!source) source = requireFile(url)
const module = { id: url, uri: url, exports:exports, source }; //according to node.js modules
// fix, add comment to show source on Chrome Dev Tools
source="//@ sourceURL="+url+"\n" + source;
//------
const anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module
anonFn(require, exports, module); // call the Fn, Execute the module
return module
} catch (err) {
console.error("Error loading module "+url, err.message);
throw err;
}
}
require.cache = {}
require.alias = {}
const initCanvas = (canvas, callback)=>{
// convert HTML events (mouse movement) to viewer changes
let lastX = 0
let lastY = 0
let pointerDown = false
const moveHandler = (ev) => {
if(!pointerDown) return
const cmd = {
worker: 'render',
dx: lastX - ev.pageX,
dy: ev.pageY - lastY
}
const shiftKey = (ev.shiftKey === true) || (ev.touches && ev.touches.length > 2)
cmd.action = shiftKey ? 'pan':'rotate'
callback(cmd)
lastX = ev.pageX
lastY = ev.pageY
ev.preventDefault()
}
const downHandler = (ev) => {
pointerDown = true
lastX = ev.pageX
lastY = ev.pageY
canvas.setPointerCapture(ev.pointerId)
ev.preventDefault()
}
const upHandler = (ev) => {
pointerDown = false
canvas.releasePointerCapture(ev.pointerId)
ev.preventDefault()
}
const wheelHandler = (ev) => {
callback({action:'zoom', dy:ev.deltaY, worker: 'render'})
ev.preventDefault()
}
canvas.onpointermove = moveHandler
canvas.onpointerdown = downHandler
canvas.onpointerup = upHandler
canvas.onwheel = wheelHandler
}
const cmdHandler = (handlers)=>(cmd)=>{
const fn = handlers[cmd.action]
if (!fn) throw new Error('no handler for type: ' + cmd.action)
fn(cmd);
}
const makeScriptWorker = ({callback, convertToSolids})=>{
let workerBaseURI, onInit
function runMain(params={}){
let time = Date.now()
let solids
try{
solids = main(params)
}catch(e){
callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}, transfer)
return
}
let solidsTime = Date.now() - time
scriptStats = `generate solids ${solidsTime}ms`
let transfer = []
if(convertToSolids === 'buffers'){
CSGToBuffers.clearCache()
entities = solids.map((csg)=>{
let obj = CSGToBuffers(csg, transfer)
obj.color = csg.color
obj.transforms = csg.transforms
return obj
})
}else if(convertToSolids === 'regl'){
const { entitiesFromSolids } = require('@jscad/regl-renderer')
time = Date.now()
entities = entitiesFromSolids({}, solids)
scriptStats += ` convert to entities ${Date.now()-time}ms`
}else{
entities = solids
}
callback({action:'entities', worker:'render', entities, scriptStats}, transfer)
}
let initialized = false
const handlers = {
runScript: ({script,url, params={}})=>{
if(!initialized){
onInit = ()=>handlers.runScript({script,url, params})
}
let script_module
try{
script_module = requireModule(url,script)
}catch(e){
callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()})
return
}
main = script_module.exports.main
let gp = script_module.exports.getParameterDefinitions
if(gp){
callback({action:'parameterDefinitions', worker:'main', data:gp()})
}
runMain(params)
},
updateParams: ({params={}})=>{
runMain(params)
},
init: (params)=>{
let {baseURI, alias=[]} = params
if(!baseURI && typeof document != 'undefined' && document.baseURI){
baseURI = document.baseURI
}
if(baseURI) workerBaseURI = baseURI.toString()
alias.forEach(arr=>{
let [orig, ...aliases] = arr
aliases.forEach(a=>{
require.alias[a] = orig
if(a.toLowerCase().substr(-3)!=='.js') require.alias[a+'.js'] = orig
})
})
initialized = true
if(onInit) onInit()
},
}
return {
// called from outside to pass mesasges into worker
postMessage: cmdHandler(handlers),
}
}
/** Make render worker */
const makeRenderWorker = ()=>{
let perspectiveCamera
const state = {}
const rotateSpeed = 0.002
const panSpeed = 1
const zoomSpeed = 0.08
let rotateDelta = [0, 0]
let panDelta = [0, 0]
let zoomDelta = 0
let updateRender = true
let orbitControls, renderOptions, gridOptions, axisOptions, renderer
let entities = []
function createContext (canvas, contextAttributes) {
function get (type) {
try {
return {gl:canvas.getContext(type, contextAttributes), type}
} catch (e) {
return null
}
}
return (
get('webgl2') ||
get('webgl') ||
get('experimental-webgl') ||
get('webgl-experimental')
)
}
const startRenderer = ({canvas, cameraPosition, cameraTarget, axis={}, grid={}})=>{
const { prepareRender, drawCommands, cameras, controls } = require('@jscad/regl-renderer')
perspectiveCamera = cameras.perspective
orbitControls = controls.orbit
state.canvas = canvas
state.camera = Object.assign({}, perspectiveCamera.defaults)
if(cameraPosition) state.camera.position = cameraPosition
if(cameraTarget) state.camera.target = cameraTarget
resize({ width:canvas.width, height:canvas.height })
state.controls = orbitControls.defaults
const {gl, type} = createContext(canvas)
// prepare the renderer
const setupOptions = {
glOptions: {gl}
}
if(type == 'webgl'){
setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint']
}
renderer = prepareRender(setupOptions)
gridOptions = {
visuals: {
drawCmd: 'drawGrid',
show: grid.show || grid.show === undefined ,
color: grid.color || [0, 0, 0, 1],
subColor: grid.subColor || [0, 0, 1, 0.5],
fadeOut: false,
transparent: true
},
size: grid.size || [200, 200],
ticks: grid.ticks || [10, 1]
}
axisOptions = {
visuals: {
drawCmd: 'drawAxis',
show: axis.show || axis.show === undefined
},
size: axis.size || 100,
}
// assemble the options for rendering
renderOptions = {
camera: state.camera,
drawCommands: {
drawAxis: drawCommands.drawAxis,
drawGrid: drawCommands.drawGrid,
drawLines: drawCommands.drawLines,
drawMesh: drawCommands.drawMesh
},
// define the visual content
entities: [
gridOptions,
axisOptions,
...entities
]
}
// the heart of rendering, as themes, controls, etc change
updateView()
}
let renderTimer
const tmFunc = typeof requestAnimationFrame === 'undefined' ? setTimeout : requestAnimationFrame
function updateView(delay=8){
if(renderTimer || !renderer) return
renderTimer = tmFunc(updateAndRender,delay)
}
const doRotatePanZoom = () => {
if (rotateDelta[0] || rotateDelta[1]) {
const updated = orbitControls.rotate({ controls: state.controls, camera: state.camera, speed: rotateSpeed }, rotateDelta)
state.controls = { ...state.controls, ...updated.controls }
rotateDelta = [0, 0]
}
if (panDelta[0] || panDelta[1]) {
const updated = orbitControls.pan({ controls:state.controls, camera:state.camera, speed: panSpeed }, panDelta)
state.controls = { ...state.controls, ...updated.controls }
panDelta = [0, 0]
state.camera.position = updated.camera.position
state.camera.target = updated.camera.target
}
if (zoomDelta) {
const updated = orbitControls.zoom({ controls:state.controls, camera:state.camera, speed: zoomSpeed }, zoomDelta)
state.controls = { ...state.controls, ...updated.controls }
zoomDelta = 0
}
}
const updateAndRender = (timestamp) => {
renderTimer = null
doRotatePanZoom()
const updates = orbitControls.update({ controls: state.controls, camera: state.camera })
state.controls = { ...state.controls, ...updates.controls }
if(state.controls.changed) updateView(16) // for elasticity in rotate / zoom
state.camera.position = updates.camera.position
perspectiveCamera.update(state.camera)
renderOptions.entities = [
gridOptions,
axisOptions,
...entities
]
let time = Date.now()
renderer(renderOptions)
if(updateRender){
console.log(updateRender, ' first render', Date.now()-time);
updateRender = '';
}
}
function resize({width,height}){
state.canvas.width = width
state.canvas.height = height
perspectiveCamera.setProjection(state.camera, state.camera, { width, height })
perspectiveCamera.update(state.camera, state.camera)
updateView()
}
const handlers = {
pan: ({dx,dy})=>{
panDelta[0] += dx
panDelta[1] += dy
updateView()
},
rotate: ({dx,dy})=>{
rotateDelta[0] -= dx
rotateDelta[1] -= dy
updateView()
},
zoom: ({dy})=>{
zoomDelta += dy
updateView()
},
resize,
entities: (params)=>{
entities = params.entities
updateRender = params.scriptStats
updateView()
},
init: (params)=>{
if(params.canvas) startRenderer(params)
initialized = true
},
}
return {
// called from outside to pass mesasges into worker
postMessage: cmdHandler(handlers),
}
}
return (params)=>{
let { canvas, baseURI=(typeof document === 'undefined') ? '':document.location.toString(), scope='main', renderInWorker, render, callback=()=>{}, scriptUrl='demo-worker.js', alias, convertToSolids=false } = params
// by default 'render' messages go outside of this instance (result of modeling)
let sendToRender = callback
let scriptWorker, renderWorker
workerBaseURI = baseURI
const sendCmd = (params, transfer)=>{
if(params.worker === 'render')
sendToRender(params, transfer)
else if(params.worker === 'script')
scriptWorker.postMessage(params, transfer)
else{
// parameter definitions will arrive from scriptWorker
callback(params, transfer)
}
}
const updateSize = function({width,height}){
sendCmd({ action:'resize', worker:'render', width: canvas.offsetWidth, height: canvas.offsetHeight})
}
renderInWorker = !!(canvas && renderInWorker && canvas.transferControlToOffscreen)
const makeRenderWorkerHere = (scope === 'main' && canvas && !renderInWorker) || (scope === 'worker' && render)
// worker is in current thread
if(makeRenderWorkerHere){
console.log('render in scope: '+scope);
renderWorker = makeRenderWorker({callback:sendCmd})
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
}
if(scope === 'main'){
// let extraScript = renderInWorker ? `,'https://unpkg.com/@jscad/regl-renderer'`:''
let script =`let baseURI = '${baseURI}'
importScripts(new URL('${scriptUrl}',baseURI))
let worker = jscadWorker({
baseURI: baseURI,
convertToSolids: ${convertToSolids},
scope:'worker',
callback:(params)=>self.postMessage(params),
render:${renderInWorker}
})
self.addEventListener('message', (e)=>worker.postMessage(e.data))
`
let blob = new Blob([script],{type: 'text/javascript'})
scriptWorker = new Worker(window.URL.createObjectURL(blob))
scriptWorker.addEventListener('message',(e)=>sendCmd(e.data))
scriptWorker.postMessage({action:'init', baseURI, alias})
if(renderInWorker) renderWorker = scriptWorker
if(canvas){
initCanvas(canvas, sendCmd)
window.addEventListener('resize',updateSize)
}
}else{
scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids})
callback({action:'workerInit',worker:'main'})
}
if(canvas){
// redirect 'render' messages to renderWorker
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
let width = canvas.width = canvas.clientWidth
let height = canvas.height = canvas.clientHeight
if(scope == 'main'){
const offscreen = renderInWorker ? canvas.transferControlToOffscreen() : canvas
renderWorker.postMessage({action:'init', worker:'render', canvas:offscreen, width, height}, [offscreen])
}
}
return {
updateSize,
updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}),
runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}),
postMessage: sendCmd,
}
}
// multi purpose module
});

View File

@@ -35,8 +35,7 @@ export const makeStlDownloadHandler =
ideType,
}: makeStlDownloadHandlerArgs) =>
() => {
const makeStlBlobFromGeo = flow(
(geo) => new Mesh(geo, new MeshBasicMaterial()),
const makeStlBlobFromMesh = flow(
(mesh) => new Scene().add(mesh),
(scene) => new STLExporter().parse(scene),
(stl) =>
@@ -44,8 +43,11 @@ export const makeStlDownloadHandler =
type: 'text/plain',
})
)
const saveFile = (geometry) => {
const blob = makeStlBlobFromGeo(geometry)
const makeStlBlobFromGeo = flow(
(geo) => new Mesh(geo, new MeshBasicMaterial()),
(mesh) => makeStlBlobFromMesh(mesh)
)
const saveFile = (blob) => {
fileSave(blob, {
fileName,
extensions: ['.stl'],
@@ -56,7 +58,9 @@ export const makeStlDownloadHandler =
type === 'geometry' &&
(quality === 'high' || ideType === 'openscad')
) {
saveFile(geometry)
saveFile(makeStlBlobFromGeo(geometry))
} else if (ideType == 'jscad') {
saveFile(makeStlBlobFromMesh(geometry))
} else {
thunkDispatch((dispatch, getState) => {
const state = getState()

View File

@@ -4,6 +4,8 @@ import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
import Editor, { useMonaco } from '@monaco-editor/react'
import { theme } from 'src/../tailwind.config'
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
import type { CadPackage as CadPackageType } from 'src/helpers/hooks/useIdeState'
import CadPackage from '../CadPackage/CadPackage'
const colors = theme.extend.colors
@@ -12,9 +14,10 @@ const IdeEditor = ({ Loading }) => {
const [theme, setTheme] = useState('vs-dark')
const saveCode = useSaveCode()
const ideTypeToLanguageMap = {
const ideTypeToLanguageMap: { [key in CadPackageType]: string } = {
cadquery: 'python',
openscad: 'cpp',
jscad: 'javascript',
}
const monaco = useMonaco()
useEffect(() => {

View File

@@ -21,6 +21,12 @@ function Asset({ geometry: incomingGeo }) {
}
}, [incomingGeo])
if (!incomingGeo) return null
if (incomingGeo.length)
return incomingGeo.map((shape, index) => (
<primitive object={shape} key={index} />
))
return (
<mesh ref={mesh} scale={[1, 1, 1]}>
<bufferGeometry attach="geometry" ref={ref} />
@@ -209,8 +215,8 @@ const IdeViewer = ({ Loading }) => {
})
}}
/>
<ambientLight intensity={1} />
<pointLight position={[15, 5, 10]} intensity={4} />
<ambientLight intensity={0.3} />
<pointLight position={[15, 5, 10]} intensity={0.1} />
<pointLight position={[-1000, -1000, -1000]} intensity={1} />
<pointLight position={[-1000, 0, 1000]} intensity={1} />
{state.objectData?.type === 'png' && (

View File

@@ -1,6 +1,21 @@
import { Link, routes } from '@redwoodjs/router'
import Svg from 'src/components/Svg/Svg'
import { Popover } from '@headlessui/react'
import type { CadPackage } from 'src/helpers/hooks/useIdeState'
const menuOptions: {
name: string
sub: string
ideType: CadPackage
}[] = [
{
name: 'OpenSCAD',
sub: 'beta',
ideType: 'openscad',
},
{ name: 'CadQuery', sub: 'beta', ideType: 'cadquery' },
// { name: 'JSCAD', sub: 'alpha', ideType: 'jscad' }, // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
]
const NavPlusButton: React.FC = () => {
return (
@@ -11,14 +26,7 @@ const NavPlusButton: React.FC = () => {
<Popover.Panel className="absolute z-10 right-0">
<ul className="bg-gray-200 mt-4 rounded shadow-md overflow-hidden">
{[
{
name: 'OpenSCAD',
sub: 'beta',
ideType: 'openscad',
},
{ name: 'CadQuery', sub: 'beta', ideType: 'cadquery' },
].map(({ name, sub, ideType }) => (
{menuOptions.map(({ name, sub, ideType }) => (
<li
key={name}
className="px-4 py-2 hover:bg-gray-400 text-gray-800"

View File

@@ -1,7 +1,12 @@
import { DefaultKernelExport } from './common'
import type { CadPackage } from 'src/helpers/hooks/useIdeState'
import openscad from './openScadController'
import cadquery from './cadQueryController'
import jscad from './jsCadController'
export const cadPackages = {
export const cadPackages: { [key in CadPackage]: DefaultKernelExport } = {
openscad,
cadquery,
jscad,
}

View File

@@ -0,0 +1,142 @@
import {
RenderArgs,
DefaultKernelExport,
createUnhealthyResponse,
createHealthyResponse,
} from './common'
import {
MeshPhongMaterial,
LineBasicMaterial,
BufferGeometry,
BufferAttribute,
Line,
LineSegments,
Color,
Mesh,
Group,
} from 'three/build/three.module'
const materials = {
mesh: {
def: new MeshPhongMaterial({ color: 0x0084d1, flatShading: true }),
material: (params) => new MeshPhongMaterial(params),
},
line: {
def: new LineBasicMaterial({ color: 0x0000ff }),
material: ({ color, opacity, transparent }) =>
new LineBasicMaterial({ color, opacity, transparent }),
},
lines: null,
}
materials.lines = materials.line
function CSG2Object3D(obj) {
const { vertices, indices, color, transforms } = obj
const materialDef = materials[obj.type]
if (!materialDef) {
console.error('Can not hangle object type: ' + obj.type, obj)
return null
}
let material = materialDef.def
if (color) {
const c = color
material = new materialDef.material({
color: new Color(c[0], c[1], c[2]),
flatShading: true,
opacity: c[3] === void 0 ? 1 : c[3],
transparent: c[3] != 1 && c[3] !== void 0,
})
}
const geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(vertices, 3))
let mesh
switch (obj.type) {
case 'mesh':
geo.setIndex(new BufferAttribute(indices, 1))
mesh = new Mesh(geo, material)
break
case 'line':
mesh = new Line(geo, material)
break
case 'lines':
mesh = new LineSegments(geo, material)
break
}
if (transforms) mesh.applyMatrix4({ elements: transforms })
return mesh
}
let scriptWorker
const scriptUrl = '/demo-worker.js'
let resolveReference = null
let response = null
const callResolve = () => {
if (resolveReference) resolveReference()
resolveReference
}
export const render: DefaultKernelExport['render'] = async ({
code,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
const baseURI = document.baseURI.toString()
const script = `let baseURI = '${baseURI}'
importScripts(new URL('${scriptUrl}',baseURI))
let worker = jscadWorker({
baseURI: baseURI,
scope:'worker',
convertToSolids: 'buffers',
callback:(params)=>self.postMessage(params),
})
self.addEventListener('message', (e)=>worker.postMessage(e.data))
`
const blob = new Blob([script], { type: 'text/javascript' })
scriptWorker = new Worker(window.URL.createObjectURL(blob))
scriptWorker.addEventListener('message', (e) => {
const data = e.data
if (data.action == 'entities') {
if (data.error) {
response = createUnhealthyResponse(new Date(), data.error)
} else {
response = createHealthyResponse({
type: 'geometry',
data: [...data.entities.map(CSG2Object3D).filter((o) => o)],
consoleMessage: data.scriptStats,
date: new Date(),
})
}
callResolve()
}
})
callResolve()
response = null
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
scriptWorker.postMessage({
action: 'runScript',
worker: 'script',
script: code,
url: 'jscad_script',
})
const waitResult = new Promise((resolve) => {
resolveReference = resolve
})
await waitResult
resolveReference = null
return response
}
const jsCadController: DefaultKernelExport = {
render,
}
export default jsCadController

View File

@@ -9,7 +9,9 @@ function withThunk(dispatch, getState) {
: dispatch(actionOrThunk)
}
const initCodeMap = {
export type CadPackage = 'openscad' | 'cadquery' | 'jscad'
const initCodeMap: { [key in CadPackage]: string } = {
openscad: `// involute donut
// ^ first comment is used for download title (i.e "involute-donut.stl")
@@ -39,6 +41,33 @@ result = (cq.Workplane().circle(diam).extrude(20.0)
.edges("%CIRCLE").chamfer(0.15))
show_object(result)
`,
jscad: `
const { booleans, colors, primitives } = require('@jscad/modeling') // modeling comes from the included MODELING library
const { intersect, subtract } = booleans
const { colorize } = colors
const { cube, cuboid, line, sphere, star } = primitives
const main = ({scale=1}) => {
const logo = [
colorize([1.0, 0.4, 1.0], subtract(
cube({ size: 300 }),
sphere({ radius: 200 })
)),
colorize([1.0, 1.0, 0], intersect(
sphere({ radius: 130 }),
cube({ size: 210 })
))
]
const transpCube = colorize([1, 0, 0, 0.75], cuboid({ size: [100 * scale, 100, 210 + (200 * scale)] }))
const star2D = star({ vertices: 8, innerRadius: 150, outerRadius: 200 })
const line2D = colorize([1.0, 0, 0], line([[220, 220], [-220, 220], [-220, -220], [220, -220], [220, 220]]))
return [transpCube, star2D, line2D, ...logo]
}
module.exports = {main}
`,
}
@@ -53,7 +82,7 @@ interface XYZ {
}
export interface State {
ideType: 'INIT' | 'openscad' | 'cadquery'
ideType: 'INIT' | CadPackage
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
code: string
objectData: {