Merge branch 'main' into keyboard-shortcuts

This commit is contained in:
Frank Noirot
2021-09-08 17:52:58 -04:00
committed by GitHub
28 changed files with 835 additions and 922 deletions

View File

@@ -1,11 +1,21 @@
import { ideTypeNameMap } from 'src/helpers/hooks/useIdeContext'
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
interface CadPackageProps {
cadPackage: string
className?: string
export const ideTypeNameMap = {
openscad: 'OpenSCAD',
cadquery: 'CadQuery',
}
const CadPackage = ({ cadPackage, className = '' }: CadPackageProps) => {
interface CadPackageProps {
cadPackage: CadPackageType
className?: string
dotClass?: string
}
const CadPackage = ({
cadPackage,
className = '',
dotClass = 'w-5 h-5',
}: CadPackageProps) => {
const cadName = ideTypeNameMap[cadPackage] || ''
const isOpenScad = cadPackage === 'openscad'
const isCadQuery = cadPackage === 'cadquery'
@@ -14,13 +24,13 @@ const CadPackage = ({ cadPackage, className = '' }: CadPackageProps) => {
className={
`grid grid-flow-col-dense items-center gap-2 cursor-default text-gray-100 ${
isOpenScad && 'bg-yellow-800'
} ${isCadQuery && 'bg-ch-blue-300'} bg-opacity-30 ` + className
} ${isCadQuery && 'bg-ch-blue-700'} bg-opacity-30 ` + className
}
>
<div
className={`${isOpenScad && 'bg-yellow-200'} ${
isCadQuery && 'bg-blue-800'
} w-5 h-5 rounded-full`}
} ${dotClass} rounded-full`}
/>
<div>{cadName}</div>
</div>

View File

@@ -222,7 +222,7 @@ function ChoiceParam({
className={({ active }) =>
`${
active
? 'text-ch-blue-600 bg-ch-gray-700'
? 'text-ch-blue-400 bg-ch-gray-700'
: 'text-ch-gray-300'
}
cursor-default select-none relative py-2 pl-10 pr-4`
@@ -241,7 +241,7 @@ function ChoiceParam({
{selected ? (
<span
className={`${
active ? 'text-ch-blue-600' : 'text-ch-gray-300'
active ? 'text-ch-blue-400' : 'text-ch-gray-300'
}
absolute inset-y-0 left-0 flex items-center pl-3`}
>

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'
import { useIdeContext, ideTypeNameMap } from 'src/helpers/hooks/useIdeContext'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { ideTypeNameMap } from 'src/components/CadPackage/CadPackage'
import OutBound from 'src/components/OutBound/OutBound'
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
import { copyTextToClipboard } from 'src/helpers/clipboard'

View File

@@ -1,7 +0,0 @@
import Footer from './Footer'
export const generated = () => {
return <Footer />
}
export default { title: 'Components/Footer' }

View File

@@ -1,11 +0,0 @@
import { render } from '@redwoodjs/testing'
import Footer from './Footer'
describe('Footer', () => {
it('renders successfully', () => {
expect(() => {
render(<Footer />)
}).not.toThrow()
})
})

View File

@@ -3,7 +3,7 @@ import OutBound from 'src/components/OutBound'
const Footer = () => {
return (
<div className="bg-indigo-900 text-indigo-200 font-roboto mt-20 text-sm">
<div className="bg-indigo-900 text-indigo-200 font-roboto text-sm">
<div className="flex h-16 md:justify-end items-center mx-2 md:mx-16 flex-wrap">
<OutBound className="mr-8" to="https://github.com/Irev-Dev/cadhub">
Github

View File

@@ -18,7 +18,7 @@ const IdeConsole = () => {
className="font-mono text-sm text-gray-400"
key={`${message} ${index}`}
>
<div className="text-xs font-bold pt-2 text-ch-blue-600">
<div className="text-xs font-bold pt-2 text-ch-blue-400">
{time?.toLocaleString()}
</div>
<div className={(type === 'error' ? 'text-red-400' : '') + ' pl-4'}>

View File

@@ -4,8 +4,7 @@ import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
import Editor, { useMonaco } from '@monaco-editor/react'
import { theme } from 'src/../config/tailwind.config'
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
import type { CadPackage as CadPackageType } from 'src/helpers/hooks/useIdeState'
import CadPackage from '../CadPackage/CadPackage'
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
const colors = theme.extend.colors

View File

@@ -102,9 +102,9 @@ const IdeHeader = ({
<TopButton
onClick={onClick}
name="Save Project Image"
className=" bg-ch-gray-300 bg-opacity-70 hover:bg-opacity-90 text-ch-gray-900"
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg name="camera" className="w-6 h-6" />
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
</TopButton>
)}
/>

View File

@@ -4,17 +4,17 @@ import Svg from 'src/components/Svg/Svg'
const IdeSideBar = () => {
return (
<div className="h-full flex flex-col justify-between">
<div className="w-16 h-16 flex items-center justify-center bg-ch-gray-900">
<div className="w-14 h-16 flex items-center justify-center bg-ch-gray-900">
<Link to={routes.home()}>
<Svg className="w-12" name="favicon" />
<Svg className="w-12 p-0.5" name="favicon" />
</Link>
</div>
<button
className="text-gray-300 p-2 pb-4 flex justify-center cursor-not-allowed"
className="text-gray-300 p-3 pb-6 flex justify-center cursor-not-allowed"
aria-label="IDE settings"
disabled
>
<Svg name="big-gear" />
<Svg name="gear" />
</button>
</div>
)

View File

@@ -1,12 +1,12 @@
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'
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
const menuOptions: {
name: string
sub: string
ideType: CadPackage
ideType: CadPackageType
}[] = [
{
name: 'OpenSCAD',
@@ -20,7 +20,7 @@ const menuOptions: {
const NavPlusButton: React.FC = () => {
return (
<Popover className="relative outline-none w-full h-full">
<Popover.Button className="h-full w-full outline-none">
<Popover.Button className="h-full w-full outline-none hover:bg-ch-gray-550 border rounded-full">
<Svg name="plus" className="text-gray-200" />
</Popover.Button>

View File

@@ -23,10 +23,10 @@ const PanelToolbar = ({
aria-label={`${panelName} settings`}
disabled
>
<Svg name="gear" className="w-7 p-px" />
<Svg name="gear" className="w-7 p-0.5" />
</button>
{mosaicWindowActions.connectDragSource(
<div className=" text-ch-gray-760 bg-ch-gray-300 cursor-grab px-2 h-full flex items-center">
<div className=" text-ch-gray-760 bg-ch-gray-300 cursor-grab px-1.5 h-full flex items-center">
<Svg name="drag-grid" className="w-4 p-px" />
</div>
)}

View File

@@ -36,7 +36,7 @@ const KeyValue = ({
if (!children || hide) return null
return (
<div>
<div className="text-ch-blue-600 font-fira-code flex text-sm whitespace-nowrap">
<div className="text-ch-blue-400 font-fira-code flex text-sm whitespace-nowrap">
{keyName}
{canEdit &&
(isEditable ? (
@@ -246,7 +246,7 @@ const ProjectProfile = ({
/>
{user?.userName}
</Link>
<div className="font-fira-code text-ch-blue-600 flex items-center">
<div className="font-fira-code text-ch-blue-400 flex items-center">
{new Date(createdAt).toDateString()}
</div>
</div>

View File

@@ -1,5 +1,7 @@
import { useMemo } from 'react'
import { Link, routes } from '@redwoodjs/router'
import Svg from 'src/components/Svg/Svg'
import CadPackage from 'src/components/CadPackage/CadPackage'
import { countEmotes } from 'src/helpers/emote'
import ImageUploader from 'src/components/ImageUploader'
@@ -11,6 +13,8 @@ const ProjectsList = ({
// temporary filtering projects that don't have images until some kind of search is added and there are more things on the website
// it helps avoid the look of the website just being filled with dumby data.
// related issue-104
// note to self the projectCard is hardcoded directly into this component will not be hard the extract later when we need it elsewhere.
const filteredProjects = useMemo(
() =>
(shouldFilterProjectsWithoutImage
@@ -24,67 +28,68 @@ const ProjectsList = ({
),
[projects, shouldFilterProjectsWithoutImage]
)
return (
<section className="max-w-6xl mx-auto mt-8">
<section className="max-w-6xl mx-auto">
<ul
className="grid gap-x-8 gap-y-12 items-center mx-4 relative"
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
>
{filteredProjects.map(({ title, mainImage, user, Reaction }) => (
<li
className="rounded-lg shadow-md hover:shadow-lg mx-px transform hover:-translate-y-px transition-all duration-150"
key={`${user?.userName}--${title}`}
>
<Link
to={routes.project({
userName: user?.userName,
projectTitle: title,
})}
{filteredProjects.map(
({ title, mainImage, user, Reaction, cadPackage }) => (
<li
className="rounded p-1.5 bg-ch-gray-760 shadow-ch"
key={`${user?.userName}--${title}`}
>
<div className="flex items-center p-2 bg-gray-200 border-gray-300 rounded-t-lg border-t border-l border-r">
<div className="w-8 h-8 overflow-hidden rounded-full border border-indigo-300 shadow">
<Link
to={routes.project({
userName: user?.userName,
projectTitle: title,
})}
>
<div className="relative">
<ImageUploader
className=""
aspectRatio={1}
imageUrl={user?.image}
width={50}
className="rounded"
aspectRatio={1.4}
imageUrl={mainImage}
width={700}
/>
<CadPackage
cadPackage={cadPackage}
className="absolute right-0 top-0 p-1.5 rounded-bl text-sm bg-opacity-50"
dotClass="w-3 h-3"
/>
</div>
<span className="font-ropa-sans ml-3 text-lg text-indigo-900">
{title}
</span>
</div>
<div className="w-full overflow-hidden relative rounded-b-lg">
<ImageUploader
className=""
aspectRatio={1.4}
imageUrl={mainImage}
width={700}
/>
<div
className="absolute inset-0"
style={{
background:
'linear-gradient(19.04deg, rgba(62, 44, 118, 0.46) 10.52%, rgba(60, 54, 107, 0) 40.02%)',
}}
/>
</div>
<div className="absolute inset-x-0 bottom-0 -mb-4 mr-4 flex justify-end">
{countEmotes(Reaction).map(({ emoji, count }) => (
<div
key={emoji}
className="h-8 w-8 overflow-hidden ml-2 p-1 rounded-full bg-opacity-75 bg-gray-200 border border-gray-300 shadow-md flex items-center justify-between"
>
<div className="-ml-px text-sm w-1">{emoji}</div>
<div className="text-sm pl-1 font-ropa-sans text-gray-800">
{count}
</div>
<div className="flex items-center mt-1">
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
<ImageUploader
className=""
aspectRatio={1}
imageUrl={user?.image}
width={50}
/>
</div>
))}
</div>
</Link>
</li>
))}
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
<div className="">{title}</div>
<div className="text-sm">{user?.userName}</div>
</div>
</div>
<div className="grid grid-flow-col-dense gap-2 justify-start mt-1.5">
<div className="px-2 flex items-center bg-ch-gray-600 text-ch-gray-300 rounded-sm">
<Svg name="reactions" className="w-4 mr-2" />
{countEmotes(Reaction).reduce(
(prev, { count }) => prev + count,
0
)}
</div>
<div className="px-2 flex items-center bg-ch-blue-650 bg-opacity-30 text-ch-gray-300 rounded-sm">
<Svg name="fork-new" className="w-4 mr-2" />0
</div>
</div>
</Link>
</li>
)
)}
</ul>
</section>
)

View File

@@ -7,6 +7,7 @@ export const QUERY = gql`
projects {
id
title
cadPackage
mainImage
createdAt
updatedAt

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -16,6 +16,7 @@ import {
Mesh,
} from 'three'
import { jsCadToCadhubParams } from './jscadParams'
import TheWorker from 'worker-loader!./jscadWorker'
const materials = {
mesh: {
@@ -134,19 +135,7 @@ export const render: DefaultKernelExport['render'] = async ({
}: RenderArgs) => {
if (!scriptWorker) {
const baseURI = document.baseURI.toString()
const scriptUrl = '/demo-worker.js'
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 = new TheWorker()
let parameterDefinitions = []
scriptWorker.addEventListener('message', ({ data }) => {
if (data.action == 'parameterDefinitions') {

View File

@@ -0,0 +1,695 @@
const setPoints = (points, p, i) => {
points[i++] = p[0]
points[i++] = p[1]
points[i++] = p[2] || 0
}
function CSG2Vertices(csg) {
const idx = 0
let vLen = 0,
iLen = 0
for (const poly of csg.polygons) {
const 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 (const poly of csg.polygons) {
const arr = poly.vertices
const 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
const 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) {
const vLen = csg.sides.length * 6
const vertices = new Float32Array(vLen)
csg.sides.forEach((side, idx) => {
const 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
const 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 {
const 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 cmdHandler = (handlers) => (cmd) => {
const fn = handlers[cmd.action]
if (!fn) throw new Error('no handler for type: ' + cmd.action)
fn(cmd)
}
function parseParams(script) {
let lines = script.split('\n').map((l) => l.trim())
lines = lines
.map((l, i) => {
return { code: l, line: i + 1, group: l[0] == '/' && !lines[i + 1] }
})
.filter((l) => l.code)
let i = 0,
line,
next,
lineNum
while (i < lines.length) {
line = lines[i].code.trim()
i++
if (line.length > 12 && line.substring(line.length - 13) == '//jscadparams')
break
if (line.length > 12 && line.indexOf('@jscad-params') != -1) break
}
let groupIndex = 1
const defs = []
while (i < lines.length) {
line = lines[i].code
lineNum = lines[i].line
next = lines[i + 1] ? lines[i + 1].code : ''
if (line[0] === '}') break
if (line[0] === '/') {
// group
const def = parseComment(line, lineNum)
let name = '_group_' + groupIndex++
let caption = def.caption
const idx = caption.lastIndexOf(':')
if (idx !== -1) {
name = caption.substring(idx + 1).trim()
caption = caption.substring(0, idx).trim()
}
defs.push({ name, type: 'group', caption, ...def.options })
} else {
const idx = line.indexOf('/')
if (idx === -1) {
const def = parseDef(line, lineNum)
def.caption = def.name
defs.push(def)
} else {
defs.push(
parseOne(
line.substring(idx).trim(),
line.substring(0, idx).trim(),
lineNum,
lineNum
)
)
}
}
i++
}
return defs
}
function parseOne(comment, code, line1, line2) {
const { caption, options } = parseComment(comment, line1)
let def = { caption, ...parseDef(code, line2) }
def.caption = def.caption || def.name
if (options) {
def = { ...def, ...options }
if (def.type === 'checkbox' && def.hasOwnProperty('initial'))
def.checked = true
if (def.type === 'slider') {
if (def.min === undefined) {
def.min = 0
}
if (def.max === undefined) {
def.max = def.initial * 2 || 100
}
}
}
return def
}
function parseComment(comment, line) {
const prefix = comment.substring(0, 2)
if (prefix === '//') comment = comment.substring(2)
if (prefix === '/*') comment = comment.substring(2, comment.length - 2)
comment = comment.trim()
const ret = {}
const idx = comment.indexOf('{')
if (idx !== -1) {
try {
ret.options = eval('(' + comment.substring(idx) + ')')
} catch (e) {
console.log('Error in line ' + line)
console.log(comment)
throw e
}
comment = comment.substring(0, idx).trim()
}
ret.caption = comment
return ret
}
function parseDef(code, line) {
if (code[code.length - 1] == ',')
code = code.substring(0, code.length - 1).trim()
let idx = code.indexOf('=')
if (idx == -1) idx = code.indexOf(':')
if (idx == -1) {
return { name: code, type: 'text' }
} else {
const initial = code.substring(idx + 1).trim()
const ret = { type: 'text', name: code.substring(0, idx).trim() }
if (initial === 'true' || initial === 'false') {
ret.type = 'checkbox'
ret.checked = initial === 'true'
} else if (/^[0-9]+$/.test(initial)) {
ret.type = 'int'
ret.initial = parseFloat(initial)
} else if (/^[0-9]+\.[0-9]+$/.test(initial)) {
ret.type = 'number'
ret.initial = parseFloat(initial)
} else {
try {
ret.initial = eval(initial)
} catch (e) {
console.log('Error in line ' + line)
console.log(code)
console.log('problem evaluating inital value:', initial)
e = new EvalError(e.message, 'code', line)
e.lineNumber = line
throw e
}
}
return ret
}
}
const makeScriptWorker = ({ callback, convertToSolids }) => {
let onInit, main, scriptStats, entities
function runMain(params = {}) {
let time = Date.now()
let solids
const transfer = []
try {
const tmp = main(params)
solids = []
function flatten(arr) {
if (arr) {
if (arr instanceof Array) arr.forEach(flatten)
else solids.push(arr)
}
}
flatten(tmp)
} catch (e) {
callback(
{
action: 'entities',
worker: 'render',
error: e.message,
stack: e.stack.toString(),
},
transfer
)
return
}
const solidsTime = Date.now() - time
scriptStats = `generate solids ${solidsTime}ms`
if (convertToSolids === 'buffers') {
CSGToBuffers.clearCache()
entities = solids
.filter((s) => s)
.map((csg) => {
const 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
const gp = script_module.exports.getParameterDefinitions
const paramsDef = parseParams(script) || []
if (gp) {
gp().forEach((p) => {
const idx = paramsDef.findIndex((old) => old.name == p.name)
if (idx === -1) {
paramsDef.push(p)
} else {
paramsDef.splice(idx, 1, p)
}
})
}
if (paramsDef.length)
callback({
action: 'parameterDefinitions',
worker: 'main',
data: paramsDef,
})
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) => {
const [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 = { ...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]
const time = Date.now()
renderer(renderOptions)
if (updateRender) {
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),
}
}
function start(params) {
const { callback = () => {}, convertToSolids = false } = params
// by default 'render' messages go outside of this instance (result of modeling)
let scriptWorker
const sendCmd = (params, transfer) => {
if (params.worker === 'script') scriptWorker.postMessage(params, transfer)
else {
callback(params, transfer)
}
}
scriptWorker = makeScriptWorker({ callback: sendCmd, convertToSolids })
callback({ action: 'workerInit', worker: 'main' })
return {
updateParams: ({ params = {} }) =>
sendCmd({ action: 'updateParams', worker: 'script', params }),
runScript: ({ script, url = '' }) =>
sendCmd({ action: 'runScript', worker: 'script', script, url }),
postMessage: sendCmd,
}
}
const init = start({
convertToSolids: 'buffers',
callback: (params) => self.postMessage(params),
})
self.onmessage = ({ data }) => {
if (data.action === 'init') {
workerBaseURI = data.baseURI
}
init.postMessage(data, null)
}

View File

@@ -17,8 +17,3 @@ export const IdeContext = createContext<IdeContextType>({
export function useIdeContext() {
return useContext(IdeContext)
}
export const ideTypeNameMap = {
openscad: 'OpenSCAD',
cadquery: 'CadQuery',
}

View File

@@ -13,10 +13,9 @@ function withThunk(dispatch, getState) {
? actionOrThunk(dispatch, getState)
: dispatch(actionOrThunk)
}
import { CadPackageType } from 'src/components/CadPackage/CadPackage'
export type CadPackage = 'openscad' | 'cadquery' | 'jscad'
const initCodeMap: { [key in CadPackage]: string } = {
const initCodeMap: { [key in CadPackageType]: string } = {
openscad: `// involute donut
// ^ first comment is used for download title (i.e "involute-donut.stl")
@@ -97,7 +96,7 @@ interface XYZ {
}
export interface State {
ideType: 'INIT' | CadPackage
ideType: 'INIT' | CadPackageType
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
code: string
objectData: {

View File

@@ -13,6 +13,13 @@
* END --- TAILWIND GENERATOR EDIT
*/
@layer base {
body {
font-family: 'Fira Sans', ui-sans-serif, system-ui, -apple-system, system-ui, "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
}
.markdown-overrides h4 {
@apply text-lg font-bold;
}

View File

@@ -12,7 +12,9 @@ const ProjectsPage = () => {
lang="en-US"
/>
<LandingSection />
<ProjectsCell shouldFilterProjectsWithoutImage />
<div className="bg-ch-gray-800 py-20">
<ProjectsCell shouldFilterProjectsWithoutImage />
</div>
</MainLayout>
)
}