Compare commits
4 Commits
release
...
kurt/serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41798682b0 | ||
|
|
bca9c531a6 | ||
|
|
9e0f1eee60 | ||
|
|
53a6639fd1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
.idea
|
||||
.history
|
||||
.DS_Store
|
||||
.env
|
||||
.netlify
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Cadhub",
|
||||
"cadquery",
|
||||
"curv",
|
||||
"Customizer",
|
||||
"Hutten",
|
||||
"cadquery",
|
||||
"jscad",
|
||||
"openscad",
|
||||
"sendmail"
|
||||
|
||||
@@ -22,9 +22,9 @@ Because Each CadPackage is it's own beast we opted to use Docker in order to giv
|
||||
## Getting your dev environment setup
|
||||
|
||||
|
||||
Clone the repo, then `cd` in the repo and app directory (the docs directory is for [learn.cadhub](https://learn.cadhub.xyz/))
|
||||
Clone the repo and `cd` in the app directory (the docs directory is for [learn.cadhub](https://learn.cadhub.xyz/))
|
||||
```
|
||||
cd cadhub/app
|
||||
cd app
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
@@ -34,7 +34,7 @@ yarn install
|
||||
|
||||
Setting up the db, you'll need to have a postgres installed locally, you can [follow this guide](https://redwoodjs.com/docs/local-postgres-setup).
|
||||
|
||||
Run the following (Note: these commands require the `DATABASE_URL` env variable to be set. if you see no result when you run `echo $DATABASE_URL`, you can set it with a command like `export DATABASE_URL=postgres://postgres:somepassword@localhost`)
|
||||
Run the following
|
||||
``` terminal
|
||||
yarn rw prisma migrate dev
|
||||
yarn rw prisma db seed
|
||||
@@ -59,27 +59,6 @@ localUser2@kurthutten.com: `abc123`
|
||||
|
||||
localAdmin@kurthutten.com: `abc123`
|
||||
|
||||
### Discord bot setup
|
||||
|
||||
To set up the discord bot to notify when users publish new content, we're using the [REST](https://discord.com/developers/docs/resources/channel#message-object) API directly, used more as a notification service rather than a bot since we are not listening to messages in the chat.
|
||||
|
||||
1. If you're setting up the bot in a dev environment, create a new discord server (the "plus" button on the left when logged into the Discord webpage). Make note of the name of the project.
|
||||
2. With [developer mode turned on](https://www.howtogeek.com/714348/how-to-enable-or-disable-developer-mode-on-discord/), right click the channel you wish the bot to announce on and select "Copy ID". Add this to `.env.defaults` as `DISCORD_CHANNEL_ID`.
|
||||
3. [create a new application](https://discord.com/developers/applications), or navigate to an existing one.
|
||||
4. Create a bot within that application. Copy the bot token and add it to `.env.defaults` as `DISCORD_TOKEN`.
|
||||
5. Go to the "URL Generator" under "OAuth2" and create a URL with scope "bot" and text permission "Send Messages".
|
||||
6. Copy the generated URL and open it in a new tab. Follow the instructions on the page to add the bot to your discord server.
|
||||
|
||||
When you next start CADHub, you should see in the logs `Discord: logged in as <bot name>` and you should see a startup message from the bot in the channel.
|
||||
|
||||
To send messages as the bot when things happen in the service, use the `sendChat` helper function:
|
||||
|
||||
```typescript
|
||||
import { sendDiscordMessage } from 'src/lib/discord'
|
||||
|
||||
sendDiscordMessage("hello world!")
|
||||
```
|
||||
|
||||
## Designs
|
||||
|
||||
In progress, though can be [seen on Figma](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/CadHub?node-id=0%3A1)
|
||||
|
||||
@@ -18,9 +18,9 @@ CLOUDINARY_API_KEY=476712943135152
|
||||
# trace | info | debug | warn | error | silent
|
||||
# LOG_LEVEL=debug
|
||||
|
||||
|
||||
# EMAIL_PASSWORD=abc123
|
||||
# DISCORD_TOKEN=abc123
|
||||
# DISCORD_CHANNEL_ID=12345
|
||||
|
||||
|
||||
# CAD_LAMBDA_BASE_URL="http://localhost:8080"
|
||||
|
||||
|
||||
4
app/.gitignore
vendored
4
app/.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
dist
|
||||
web/types/graphql.d.ts
|
||||
api/types/graphql.d.ts
|
||||
|
||||
|
||||
# Deployment
|
||||
.serverless
|
||||
|
||||
@@ -1 +1 @@
|
||||
16
|
||||
lts/*
|
||||
|
||||
2547
app/api/backend.tldr
Normal file
2547
app/api/backend.tldr
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "CadPackage" ADD VALUE 'curv';
|
||||
@@ -5,7 +5,7 @@ datasource db {
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "darwin-arm64", "darwin"]
|
||||
binaryTargets = ["native", "rhel-openssl-1.0.x"]
|
||||
}
|
||||
|
||||
// sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode
|
||||
@@ -37,8 +37,7 @@ model User {
|
||||
enum CadPackage {
|
||||
openscad
|
||||
cadquery
|
||||
jscad
|
||||
curv
|
||||
jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
|
||||
}
|
||||
|
||||
model Project {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"@redwoodjs/api": "^0.38.1",
|
||||
"@redwoodjs/graphql-server": "^0.38.1",
|
||||
"@sentry/node": "^6.5.1",
|
||||
"axios": "^0.25.0",
|
||||
"axios": "^0.21.1",
|
||||
"cloudinary": "^1.23.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
@@ -17,8 +17,11 @@
|
||||
"serverless-binary-cors": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@netlify/zip-it-and-ship-it": "^4.30.0",
|
||||
"@types/nodemailer": "^6.4.2",
|
||||
"concurrently": "^6.0.0",
|
||||
"nodemon": "^2.0.7"
|
||||
"nodemon": "^2.0.7",
|
||||
"serverless-dotenv-plugin": "^3.10.0",
|
||||
"serverless-plugin-git-variables": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,8 @@ const makeRequest = (route, port) => [
|
||||
|
||||
app.post(...makeRequest('/openscad/preview', 5052))
|
||||
app.post(...makeRequest('/openscad/stl', 5053))
|
||||
|
||||
app.post(...makeRequest('/cadquery/stl', 5060))
|
||||
|
||||
app.post(...makeRequest('/curv/preview', 5070))
|
||||
app.post(...makeRequest('/curv/stl', 5071))
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening at http://localhost:${port}`)
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN apt-get update -qq
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# install node14, see comment at the to of node14source_setup.sh
|
||||
ADD src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||
ADD api/src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||
RUN ["chmod", "+x", "/nodesource_setup.sh"]
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs
|
||||
@@ -29,20 +29,21 @@ RUN apt-get update && \
|
||||
# Add the lambda emulator for local dev, (see entrypoint.sh for where it's used),
|
||||
# I have the file locally (gitignored) to speed up build times (as it downloads everytime),
|
||||
# but you can use the http version of the below ADD command or download it yourself from that url.
|
||||
ADD src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
ADD api/src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
# ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
RUN ["chmod", "+x", "/usr/local/bin/aws-lambda-rie"]
|
||||
|
||||
WORKDIR /var/task/
|
||||
# aws-lambda-ric does not play nice with yarn, so installing it seperately,
|
||||
# circle back to this later for a proper solution
|
||||
COPY package*.json /var/task/
|
||||
RUN npm install
|
||||
RUN npm install aws-lambda-ric@1.0.0
|
||||
|
||||
RUN conda --version
|
||||
|
||||
# Install CadQuery
|
||||
# RUN conda install -c cadquery -c conda-forge cadquery=master ocp=7.5.2 python=3.8
|
||||
# RUN conda info
|
||||
RUN conda install -c cadquery -c conda-forge cadquery=master ocp=7.5.2 python=3.8
|
||||
RUN conda info
|
||||
|
||||
# Get a copy of cq-cli from GitHub
|
||||
RUN git clone https://github.com/CadQuery/cq-cli.git
|
||||
@@ -53,10 +54,13 @@ RUN apt-get install -y libglew2.1
|
||||
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
|
||||
|
||||
# using built javascript from dist
|
||||
# run `yarn rw build` before bulding this image
|
||||
COPY dist/docker/cadquery/*.js /var/task/js/
|
||||
COPY dist/docker/common/*.js /var/task/common/
|
||||
COPY src/docker/common/entrypoint.sh /entrypoint.sh
|
||||
# run `yarn rw build` and $(npm bin)/zip-it-and-ship-it api/dist/functions/ api/dist/zipball before bulding this image
|
||||
COPY api/dist/zipball/cadquery.zip /var/task/
|
||||
# -n stops aws-lamda-ric from being overridden.
|
||||
RUN unzip -n /var/task/cadquery.zip
|
||||
|
||||
COPY api/src/docker/common/entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
CMD [ "js/cadquery.stl" ]
|
||||
CMD [ "cadquery.stl" ]
|
||||
|
||||
@@ -3,25 +3,19 @@ import middy from 'middy'
|
||||
import { cors } from 'middy/middlewares'
|
||||
import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils'
|
||||
|
||||
const stl = async (req, _context, callback) => {
|
||||
const _stl = async (req, _context, callback) => {
|
||||
_context.callbackWaitsForEmptyEventLoop = false
|
||||
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||
console.log('eventBody', eventBody)
|
||||
|
||||
const { file, settings } = JSON.parse(eventBody)
|
||||
const { error, fullPath } = await runCQ({
|
||||
file,
|
||||
settings,
|
||||
})
|
||||
const { error, consoleMessage, fullPath } = await runCQ({ file, settings })
|
||||
await storeAssetAndReturnUrl({
|
||||
error,
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage: '',
|
||||
tempFile : '',
|
||||
consoleMessage,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stl: middy(loggerWrap(stl)).use(cors()),
|
||||
}
|
||||
export const stl = middy(loggerWrap(_stl)).use(cors())
|
||||
|
||||
@@ -30,32 +30,31 @@ export const runCQ = async ({
|
||||
].join(' ')
|
||||
console.log('command', command)
|
||||
let consoleMessage = ''
|
||||
return { error: 'python execution currently disabled, see: https://github.com/Irev-Dev/cadhub/issues/611', fullPath }
|
||||
// try {
|
||||
// consoleMessage = await runCommand(command, 30000)
|
||||
// const params = JSON.parse(
|
||||
// await readFile(customizerPath, { encoding: 'ascii' })
|
||||
// )
|
||||
// await writeFiles(
|
||||
// [
|
||||
// {
|
||||
// file: JSON.stringify({
|
||||
// customizerParams: params,
|
||||
// consoleMessage,
|
||||
// type: 'stl',
|
||||
// }),
|
||||
// fileName: 'metadata.json',
|
||||
// },
|
||||
// ],
|
||||
// tempFile
|
||||
// )
|
||||
// await runCommand(
|
||||
// `cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||
// 15000,
|
||||
// true
|
||||
// )
|
||||
// return { consoleMessage, fullPath, tempFile }
|
||||
// } catch (error) {
|
||||
// return { error: consoleMessage || error, fullPath }
|
||||
// }
|
||||
try {
|
||||
consoleMessage = await runCommand(command, 30000)
|
||||
const params = JSON.parse(
|
||||
await readFile(customizerPath, { encoding: 'ascii' })
|
||||
)
|
||||
await writeFiles(
|
||||
[
|
||||
{
|
||||
file: JSON.stringify({
|
||||
customizerParams: params,
|
||||
consoleMessage,
|
||||
type: 'stl',
|
||||
}),
|
||||
fileName: 'metadata.json',
|
||||
},
|
||||
],
|
||||
tempFile
|
||||
)
|
||||
await runCommand(
|
||||
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||
15000,
|
||||
true
|
||||
)
|
||||
return { consoleMessage, fullPath }
|
||||
} catch (error) {
|
||||
return { error: consoleMessage || error, fullPath }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,13 +104,11 @@ export async function storeAssetAndReturnUrl({
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage,
|
||||
tempFile,
|
||||
}: {
|
||||
error: string
|
||||
callback: Function
|
||||
fullPath: string
|
||||
consoleMessage: string
|
||||
tempFile: string
|
||||
}) {
|
||||
if (error) {
|
||||
const response = {
|
||||
@@ -126,7 +124,6 @@ export async function storeAssetAndReturnUrl({
|
||||
|
||||
try {
|
||||
buffer = await readFile(fullPath, { encoding: 'base64' })
|
||||
await runCommand(`rm -R /tmp/${tempFile}`)
|
||||
} catch (e) {
|
||||
console.log('read file error', e)
|
||||
const response = {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
FROM public.ecr.aws/lts/ubuntu:20.04_stable
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
RUN apt-get update --fix-missing -qq
|
||||
RUN apt-get update --fix-missing && apt-get -y -qq install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates xvfb
|
||||
|
||||
RUN apt-get update -qq
|
||||
|
||||
RUN apt-get -y -qq install git \
|
||||
software-properties-common \
|
||||
xvfb unzip maim clang cmake \
|
||||
git-core libboost-all-dev \
|
||||
libopenexr-dev libtbb-dev \
|
||||
libglm-dev libpng-dev \
|
||||
libeigen3-dev dbus-x11 \
|
||||
libxcursor-dev libxinerama-dev \
|
||||
libxrandr-dev libglu1-mesa-dev \
|
||||
libgles2-mesa-dev libgl1-mesa-dev \
|
||||
libxi-dev
|
||||
|
||||
# Use commit to make sure build is reproduceable
|
||||
RUN git clone --recursive https://github.com/curv3d/curv && \
|
||||
cd curv && \
|
||||
git checkout b849eb57fba121f9f218dc065dc1f5ebc619836d && \
|
||||
make && make install
|
||||
|
||||
# install node14, see comment at the top of node14source_setup.sh
|
||||
ADD src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||
RUN ["chmod", "+x", "/nodesource_setup.sh"]
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install aws-lambda-cpp build dependencies, this is for the post install script in aws-lambda-ric (in package.json)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
make \
|
||||
cmake \
|
||||
unzip \
|
||||
automake autoconf libtool \
|
||||
libcurl4-openssl-dev
|
||||
|
||||
# Add the lambda emulator for local dev, (see entrypoint.sh for where it's used),
|
||||
# I have the file locally (gitignored) to speed up build times (as it downloads everytime),
|
||||
# but you can use the http version of the below ADD command or download it yourself from that url.
|
||||
ADD src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
# ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
RUN ["chmod", "+x", "/usr/local/bin/aws-lambda-rie"]
|
||||
|
||||
WORKDIR /var/task/
|
||||
COPY package*.json /var/task/
|
||||
RUN npm install
|
||||
RUN npm install aws-lambda-ric@1.0.0
|
||||
|
||||
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
|
||||
|
||||
# using built javascript from dist
|
||||
# run `yarn rw build` before bulding this image
|
||||
COPY dist/docker/curv/* /var/task/js/
|
||||
COPY dist/docker/common/* /var/task/common/
|
||||
COPY src/docker/common/entrypoint.sh /entrypoint.sh
|
||||
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
CMD [ "js/curv.preview" ]
|
||||
@@ -1,48 +0,0 @@
|
||||
import { runCurv, stlExport } from './runCurv'
|
||||
import middy from 'middy'
|
||||
import { cors } from 'middy/middlewares'
|
||||
import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils'
|
||||
|
||||
const preview = async (req, _context, callback) => {
|
||||
_context.callbackWaitsForEmptyEventLoop = false
|
||||
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||
console.log('eventBody', eventBody)
|
||||
|
||||
const { file, settings } = JSON.parse(eventBody)
|
||||
const { error, consoleMessage, fullPath, tempFile } = await runCurv({
|
||||
file,
|
||||
settings,
|
||||
})
|
||||
await storeAssetAndReturnUrl({
|
||||
error,
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage,
|
||||
tempFile,
|
||||
})
|
||||
}
|
||||
|
||||
const stl = async (req, _context, callback) => {
|
||||
_context.callbackWaitsForEmptyEventLoop = false
|
||||
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||
|
||||
console.log(eventBody, 'eventBody')
|
||||
|
||||
const { file, settings } = JSON.parse(eventBody)
|
||||
const { error, consoleMessage, fullPath, tempFile } = await stlExport({
|
||||
file,
|
||||
settings,
|
||||
})
|
||||
await storeAssetAndReturnUrl({
|
||||
error,
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage,
|
||||
tempFile,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stl: middy(loggerWrap(stl)).use(cors()),
|
||||
preview: middy(loggerWrap(preview)).use(cors()),
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { writeFiles, runCommand } from '../common/utils'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
export const runCurv = async ({
|
||||
file,
|
||||
settings: { size: { x = 500, y = 500 } = {}, parameters } = {}, // TODO add view settings
|
||||
} = {}): Promise<{
|
||||
error?: string
|
||||
consoleMessage?: string
|
||||
fullPath?: string
|
||||
customizerPath?: string
|
||||
tempFile?: string
|
||||
}> => {
|
||||
const tempFile = await writeFiles(
|
||||
[
|
||||
{ file, fileName: 'main.curv' },
|
||||
{
|
||||
file: JSON.stringify({
|
||||
parameterSets: { default: parameters },
|
||||
fileFormatVersion: '1',
|
||||
}),
|
||||
fileName: 'params.json',
|
||||
},
|
||||
],
|
||||
'a' + nanoid() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
|
||||
)
|
||||
const fullPath = `/tmp/${tempFile}/output.gz`
|
||||
const imPath = `/tmp/${tempFile}/output.png`
|
||||
const customizerPath = `/tmp/${tempFile}/customizer.param`
|
||||
|
||||
const command = [
|
||||
'xvfb-run --auto-servernum --server-args "-screen 0 3840x2160x24" curv',
|
||||
`-o ${imPath}`,
|
||||
`-O xsize=${x}`,
|
||||
`-O ysize=${y}`,
|
||||
`-O bg=webRGB[26,26,29]`, // #1A1A1D
|
||||
`/tmp/${tempFile}/main.curv`,
|
||||
].join(' ')
|
||||
console.log('command', command)
|
||||
|
||||
try {
|
||||
const consoleMessage = await runCommand(command, 15000)
|
||||
await writeFiles(
|
||||
[
|
||||
{
|
||||
file: JSON.stringify({
|
||||
consoleMessage,
|
||||
type: 'png',
|
||||
}),
|
||||
fileName: 'metadata.json',
|
||||
},
|
||||
],
|
||||
tempFile
|
||||
)
|
||||
await runCommand(
|
||||
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||
15000
|
||||
)
|
||||
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||
} catch (dirtyError) {
|
||||
return { error: dirtyError }
|
||||
}
|
||||
}
|
||||
|
||||
export const stlExport = async ({ file, settings: { parameters } } = {}) => {
|
||||
const tempFile = await writeFiles(
|
||||
[
|
||||
{ file, fileName: 'main.curv' },
|
||||
{
|
||||
file: JSON.stringify({
|
||||
parameterSets: { default: parameters },
|
||||
fileFormatVersion: '1',
|
||||
}),
|
||||
fileName: 'params.json',
|
||||
},
|
||||
],
|
||||
'a' + nanoid() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
|
||||
)
|
||||
const fullPath = `/tmp/${tempFile}/output.gz`
|
||||
const stlPath = `/tmp/${tempFile}/output.stl`
|
||||
const command = [
|
||||
'(cd /tmp && curv',
|
||||
'-o',
|
||||
stlPath,
|
||||
'-O jit',
|
||||
'-O vcount=350000',
|
||||
`/tmp/${tempFile}/main.curv`,
|
||||
')',
|
||||
].join(' ')
|
||||
|
||||
try {
|
||||
// lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
|
||||
const consoleMessage = await runCommand(command, 60000)
|
||||
await writeFiles(
|
||||
[
|
||||
{
|
||||
file: JSON.stringify({
|
||||
consoleMessage,
|
||||
type: 'stl',
|
||||
}),
|
||||
fileName: 'metadata.json',
|
||||
},
|
||||
],
|
||||
tempFile
|
||||
)
|
||||
await runCommand(
|
||||
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath} && rm ${stlPath}`,
|
||||
15000
|
||||
)
|
||||
return { consoleMessage, fullPath, tempFile }
|
||||
} catch (error) {
|
||||
return { error, fullPath }
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ services:
|
||||
|
||||
openscad-preview:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./src/docker/openscad/Dockerfile
|
||||
context: ../../../
|
||||
dockerfile: ./api/src/docker/openscad/Dockerfile
|
||||
image: openscad
|
||||
command: js/openscad.preview
|
||||
command: openscad.preview
|
||||
# Adding volumes so that the containers can be restarted for js only changes in local dev
|
||||
volumes:
|
||||
- ../../dist/docker/openscad:/var/task/js/
|
||||
- ../../dist/docker/common:/var/task/common/
|
||||
- ../dist/docker/openscad:/var/task/js/
|
||||
- ../dist/docker/common:/var/task/common/
|
||||
ports:
|
||||
- "5052:8080"
|
||||
environment:
|
||||
@@ -20,9 +20,9 @@ services:
|
||||
openscad-stl:
|
||||
image: openscad
|
||||
volumes:
|
||||
- ../../dist/docker/openscad:/var/task/js/
|
||||
- ../../dist/docker/common:/var/task/common/
|
||||
command: js/openscad.stl
|
||||
- ../dist/docker/openscad:/var/task/js/
|
||||
- ../dist/docker/common:/var/task/common/
|
||||
command: openscad.stl
|
||||
ports:
|
||||
- "5053:8080"
|
||||
environment:
|
||||
@@ -32,12 +32,12 @@ services:
|
||||
|
||||
cadquery-stl:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./src/docker/cadquery/Dockerfile
|
||||
context: ../../../
|
||||
dockerfile: ./api/src/docker/cadquery/Dockerfile
|
||||
volumes:
|
||||
- ../../dist/docker/cadquery:/var/task/js/
|
||||
- ../../dist/docker/common:/var/task/common/
|
||||
command: js/cadquery.stl
|
||||
- ../dist/docker/cadquery:/var/task/js/
|
||||
- ../dist/docker/common:/var/task/common/
|
||||
command: cadquery.stl
|
||||
ports:
|
||||
- 5060:8080
|
||||
environment:
|
||||
@@ -45,28 +45,3 @@ services:
|
||||
AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}"
|
||||
BUCKET: "${DEV_BUCKET}"
|
||||
|
||||
curv-preview:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./src/docker/curv/Dockerfile
|
||||
image: curv
|
||||
command: js/curv.preview
|
||||
# Adding volumes so that the containers can be restarted for js only changes in local dev
|
||||
volumes:
|
||||
- ../../dist/docker/curv:/var/task/js/
|
||||
- ../../dist/docker/common:/var/task/common/
|
||||
ports:
|
||||
- "5070:8080"
|
||||
curv-stl:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./src/docker/curv/Dockerfile
|
||||
image: curv
|
||||
command: js/curv.stl
|
||||
# Adding volumes so that the containers can be restarted for js only changes in local dev
|
||||
volumes:
|
||||
- ../../dist/docker/curv:/var/task/js/
|
||||
- ../../dist/docker/common:/var/task/common/
|
||||
ports:
|
||||
- "5071:8080"
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN apt-get update -qq
|
||||
RUN apt-get install -y openscad-nightly
|
||||
|
||||
# install node14, see comment at the to of node14source_setup.sh
|
||||
ADD src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||
ADD api/src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||
RUN ["chmod", "+x", "/nodesource_setup.sh"]
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs
|
||||
@@ -32,13 +32,14 @@ RUN apt-get update && \
|
||||
# Add the lambda emulator for local dev, (see entrypoint.sh for where it's used),
|
||||
# I have the file locally (gitignored) to speed up build times (as it downloads everytime),
|
||||
# but you can use the http version of the below ADD command or download it yourself from that url.
|
||||
ADD src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
ADD api/src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
# ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||
RUN ["chmod", "+x", "/usr/local/bin/aws-lambda-rie"]
|
||||
|
||||
WORKDIR /var/task/
|
||||
# aws-lambda-ric does not play nice with yarn, so installing it seperately,
|
||||
# circle back to this later for a proper solution
|
||||
COPY package*.json /var/task/
|
||||
RUN npm install
|
||||
RUN npm install aws-lambda-ric@1.0.0
|
||||
|
||||
# Install OpenSCAD libraries
|
||||
@@ -47,16 +48,19 @@ RUN echo "OPENSCADPATH=/var/task/openscad" >>/etc/profile && \
|
||||
wget -P /var/task/openscad/ https://github.com/Irev-Dev/Round-Anything/archive/refs/tags/1.0.4.zip && \
|
||||
unzip /var/task/openscad/1.0.4
|
||||
# Add our own theming (based on DeepOcean with a different "background" and "opencsg-face-back")
|
||||
COPY src/docker/openscad/cadhubtheme.json /usr/share/openscad-nightly/color-schemes/render/
|
||||
COPY api/src/docker/openscad/cadhubtheme.json /usr/share/openscad-nightly/color-schemes/render/
|
||||
|
||||
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
|
||||
|
||||
# using built javascript from dist
|
||||
# run `yarn rw build` before bulding this image
|
||||
COPY dist/docker/openscad/* /var/task/js/
|
||||
COPY dist/docker/common/* /var/task/common/
|
||||
COPY src/docker/common/entrypoint.sh /entrypoint.sh
|
||||
# run `yarn rw build` and $(npm bin)/zip-it-and-ship-it api/dist/functions/ api/dist/zipball before bulding this image
|
||||
COPY api/dist/zipball/openscad.zip /var/task/
|
||||
# -n stops aws-lamda-ric from being overridden.
|
||||
RUN unzip -n /var/task/openscad.zip
|
||||
|
||||
COPY api/src/docker/common/entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
CMD [ "js/openscad.render" ]
|
||||
CMD [ "openscad.preview" ]
|
||||
|
||||
@@ -3,13 +3,13 @@ import middy from 'middy'
|
||||
import { cors } from 'middy/middlewares'
|
||||
import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils'
|
||||
|
||||
const preview = async (req, _context, callback) => {
|
||||
const _preview = async (req, _context, callback) => {
|
||||
_context.callbackWaitsForEmptyEventLoop = false
|
||||
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||
console.log('eventBody', eventBody)
|
||||
|
||||
const { file, settings } = JSON.parse(eventBody)
|
||||
const { error, consoleMessage, fullPath, tempFile } = await runScad({
|
||||
const { error, consoleMessage, fullPath } = await runScad({
|
||||
file,
|
||||
settings,
|
||||
})
|
||||
@@ -18,18 +18,17 @@ const preview = async (req, _context, callback) => {
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage,
|
||||
tempFile,
|
||||
})
|
||||
}
|
||||
|
||||
const stl = async (req, _context, callback) => {
|
||||
const _stl = async (req, _context, callback) => {
|
||||
_context.callbackWaitsForEmptyEventLoop = false
|
||||
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||
|
||||
console.log(eventBody, 'eventBody')
|
||||
|
||||
const { file, settings } = JSON.parse(eventBody)
|
||||
const { error, consoleMessage, fullPath, tempFile } = await stlExport({
|
||||
const { error, consoleMessage, fullPath } = await stlExport({
|
||||
file,
|
||||
settings,
|
||||
})
|
||||
@@ -38,11 +37,8 @@ const stl = async (req, _context, callback) => {
|
||||
callback,
|
||||
fullPath,
|
||||
consoleMessage,
|
||||
tempFile,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stl: middy(loggerWrap(stl)).use(cors()),
|
||||
preview: middy(loggerWrap(preview)).use(cors()),
|
||||
}
|
||||
export const stl = middy(loggerWrap(_stl)).use(cors())
|
||||
export const preview = middy(loggerWrap(_preview)).use(cors())
|
||||
|
||||
@@ -25,7 +25,6 @@ export const runScad = async ({
|
||||
consoleMessage?: string
|
||||
fullPath?: string
|
||||
customizerPath?: string
|
||||
tempFile?: string
|
||||
}> => {
|
||||
const tempFile = await writeFiles(
|
||||
[
|
||||
@@ -89,7 +88,7 @@ export const runScad = async ({
|
||||
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||
15000
|
||||
)
|
||||
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||
return { consoleMessage, fullPath, customizerPath }
|
||||
} catch (dirtyError) {
|
||||
return { error: cleanOpenScadError(dirtyError) }
|
||||
}
|
||||
@@ -144,7 +143,7 @@ export const stlExport = async ({ file, settings: { parameters } } = {}) => {
|
||||
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||
15000
|
||||
)
|
||||
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||
return { consoleMessage, fullPath, customizerPath }
|
||||
} catch (error) {
|
||||
return { error, fullPath }
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
service: cad-lambdas
|
||||
# app and org for use with dashboard.serverless.com
|
||||
#app: your-app-name
|
||||
#org: your-org-name
|
||||
|
||||
plugins:
|
||||
- serverless-binary-cors
|
||||
# - serverless-offline
|
||||
|
||||
# You can pin your service to only deploy with a specific Serverless version
|
||||
# Check out our docs for more details
|
||||
frameworkVersion: '2'
|
||||
|
||||
provider:
|
||||
name: aws
|
||||
lambdaHashingVersion: 20201221
|
||||
ecr:
|
||||
images:
|
||||
# this image is built locally and push to ECR
|
||||
openscadimage:
|
||||
path: ../../
|
||||
file: ./src/docker/openscad/Dockerfile
|
||||
cadqueryimage:
|
||||
path: ../../
|
||||
file: ./src/docker/cadquery/Dockerfile
|
||||
curvimage:
|
||||
path: ../../
|
||||
file: ./src/docker/curv/Dockerfile
|
||||
apiGateway:
|
||||
metrics: true
|
||||
binaryMediaTypes:
|
||||
# we need to allow binary types to be able to send back images and stls, but it would be better to be more specific
|
||||
# ie image/png etc. as */* treats everything as binary including the json body as the input the lambdas
|
||||
# which mean we need to decode the input bode from base64, but the images break with anything other than */* :(
|
||||
- '*/*'
|
||||
|
||||
# you can overwrite defaults here
|
||||
# stage: dev
|
||||
# region: us-east-1
|
||||
|
||||
# you can add statements to the Lambda function's IAM Role here
|
||||
iam:
|
||||
role:
|
||||
statements:
|
||||
- Effect: "Allow"
|
||||
Action:
|
||||
- "s3:GetObject"
|
||||
Resource: "arn:aws:s3:::cad-preview-bucket-prod-001/*"
|
||||
- Effect: "Allow"
|
||||
Action:
|
||||
- "s3:PutObject"
|
||||
Resource: "arn:aws:s3:::cad-preview-bucket-prod-001/*"
|
||||
# Dev bucket is cad-preview-bucket-dev-001/*"
|
||||
|
||||
# you can define service wide environment variables here
|
||||
# environment:
|
||||
# variable1: value1
|
||||
|
||||
functions:
|
||||
openscadpreview:
|
||||
image:
|
||||
name: openscadimage
|
||||
command:
|
||||
- js/openscad.preview
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: openscad/preview
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 25
|
||||
memorySize: 2048
|
||||
environment:
|
||||
BUCKET: cad-preview-bucket-prod-001
|
||||
openscadstl:
|
||||
image:
|
||||
name: openscadimage
|
||||
command:
|
||||
- js/openscad.stl
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: openscad/stl
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 30
|
||||
environment:
|
||||
BUCKET: cad-preview-bucket-prod-001
|
||||
|
||||
cadquerystl:
|
||||
image:
|
||||
name: cadqueryimage
|
||||
command:
|
||||
- js/cadquery.stl
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: cadquery/stl
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 30
|
||||
environment:
|
||||
BUCKET: cad-preview-bucket-prod-001
|
||||
|
||||
curvpreview:
|
||||
image:
|
||||
name: curvimage
|
||||
command:
|
||||
- js/curv.preview
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: curv/preview
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 25
|
||||
memorySize: 3008
|
||||
curvstl:
|
||||
image:
|
||||
name: curvimage
|
||||
command:
|
||||
- js/curv.stl
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: curv/stl
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 30
|
||||
# The following are a few example events you can configure
|
||||
# NOTE: Please make sure to change your handler code to work with those events
|
||||
# Check the event documentation for details
|
||||
# events:
|
||||
# - httpApi:
|
||||
# path: /users/create
|
||||
# method: get
|
||||
# - websocket: $connect
|
||||
# - s3: ${env:BUCKET}
|
||||
# - schedule: rate(10 minutes)
|
||||
# - sns: greeter-topic
|
||||
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
|
||||
# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
|
||||
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
|
||||
# - iot:
|
||||
# sql: "SELECT * FROM 'some_topic'"
|
||||
# - cloudwatchEvent:
|
||||
# event:
|
||||
# source:
|
||||
# - "aws.ec2"
|
||||
# detail-type:
|
||||
# - "EC2 Instance State-change Notification"
|
||||
# detail:
|
||||
# state:
|
||||
# - pending
|
||||
# - cloudwatchLog: '/aws/lambda/hello'
|
||||
# - cognitoUserPool:
|
||||
# pool: MyUserPool
|
||||
# trigger: PreSignUp
|
||||
# - alb:
|
||||
# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/
|
||||
# priority: 1
|
||||
# conditions:
|
||||
# host: example.com
|
||||
# path: /hello
|
||||
|
||||
# Define function environment variables here
|
||||
# environment:
|
||||
# variable2: value2
|
||||
|
||||
# you can add CloudFormation resource templates here
|
||||
#resources:
|
||||
# Resources:
|
||||
# NewResource:
|
||||
# Type: AWS::S3::Bucket
|
||||
# Properties:
|
||||
# BucketName: my-new-bucket
|
||||
# Outputs:
|
||||
# NewOutput:
|
||||
# Description: "Description for the output"
|
||||
# Value: "Some output value"
|
||||
resources:
|
||||
Resources:
|
||||
GatewayResponseDefault4XX:
|
||||
Type: 'AWS::ApiGateway::GatewayResponse'
|
||||
Properties:
|
||||
ResponseParameters:
|
||||
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
|
||||
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
|
||||
ResponseType: DEFAULT_4XX
|
||||
RestApiId:
|
||||
Ref: 'ApiGatewayRestApi'
|
||||
GatewayResponseDefault5XX:
|
||||
Type: 'AWS::ApiGateway::GatewayResponse'
|
||||
Properties:
|
||||
ResponseParameters:
|
||||
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
|
||||
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
|
||||
ResponseType: DEFAULT_5XX
|
||||
RestApiId:
|
||||
Ref: 'ApiGatewayRestApi'
|
||||
3
app/api/src/functions/cadquery.ts
Normal file
3
app/api/src/functions/cadquery.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { stl } from 'src/docker/cadquery/cadquery'
|
||||
|
||||
export { stl }
|
||||
@@ -16,6 +16,10 @@ export const handler = createGraphQLHandler({
|
||||
sdls,
|
||||
services,
|
||||
plugins: [createSentryApolloPlugin()],
|
||||
cors: {
|
||||
origin: '*',
|
||||
credentials: true,
|
||||
},
|
||||
|
||||
onException: () => {
|
||||
// Disconnect from your database with an unhandled exception.
|
||||
|
||||
3
app/api/src/functions/openscad.ts
Normal file
3
app/api/src/functions/openscad.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { stl, preview } from 'src/docker/openscad/openscad'
|
||||
|
||||
export { stl, preview }
|
||||
@@ -19,12 +19,10 @@ export const schema = gql`
|
||||
childForks: [Project]!
|
||||
}
|
||||
|
||||
# should match enum in api/db/schema.prisma
|
||||
enum CadPackage {
|
||||
openscad
|
||||
cadquery
|
||||
jscad
|
||||
curv
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
let inst = null;
|
||||
if (!process.env.DISCORD_TOKEN || !process.env.DISCORD_CHANNEL_ID) {
|
||||
console.warn("Discord bot not configured - please set process.env.DISCORD_TOKEN and process.env.DISCORD_CHANNEL_ID to send discord chats");
|
||||
} else {
|
||||
inst = axios.create({
|
||||
baseURL: 'https://discord.com/api'
|
||||
});
|
||||
inst.defaults.headers.common['Authorization'] = `Bot ${process.env.DISCORD_TOKEN}`
|
||||
console.log(`Discord: using API token ${process.env.DISCORD_TOKEN}`);
|
||||
}
|
||||
|
||||
export async function sendDiscordMessage(text: string, url?: string) {
|
||||
if (!inst) {
|
||||
console.error(`Discord: not configured to send message ("${text}")`);
|
||||
} else {
|
||||
const API_URL = `/channels/${process.env.DISCORD_CHANNEL_ID}/messages`;
|
||||
if (url) {
|
||||
return inst.post(API_URL, { embeds: [{
|
||||
title: text,
|
||||
image: {
|
||||
url,
|
||||
},
|
||||
}] });
|
||||
} else {
|
||||
return inst.post(API_URL, {
|
||||
content: text,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from 'src/services/helpers'
|
||||
import { requireAuth } from 'src/lib/auth'
|
||||
import { requireOwnership, requireProjectOwnership } from 'src/lib/owner'
|
||||
import { sendDiscordMessage } from 'src/lib/discord'
|
||||
|
||||
|
||||
export const projects = ({ userName }) => {
|
||||
if (!userName) {
|
||||
@@ -245,19 +243,7 @@ export const updateProjectImages = async ({
|
||||
const [updatedProject] = await Promise.all([
|
||||
projectPromise,
|
||||
imageDestroyPromise,
|
||||
]).then(async (result) => {
|
||||
const { userName } = await db.user.findUnique({
|
||||
where: { id: project.userId },
|
||||
})
|
||||
sendDiscordMessage([
|
||||
`${userName} just added an image to their ${project.cadPackage} project:`,
|
||||
` => ${project.title}`,
|
||||
``,
|
||||
`Check it out, leave a comment, make them feel welcome!`,
|
||||
`https://cadhub.xyz/u/${userName}/${project.title}`
|
||||
].join('\n'), `https://res.cloudinary.com/irevdev/image/upload/c_scale,w_700/v1/${mainImage}`)
|
||||
return result
|
||||
})
|
||||
])
|
||||
return updatedProject
|
||||
}
|
||||
|
||||
@@ -291,11 +277,8 @@ export const Project = {
|
||||
forkedFrom: (_obj, { root }) =>
|
||||
root.forkedFromId &&
|
||||
db.project.findUnique({ where: { id: root.forkedFromId } }),
|
||||
childForks: (_obj, { root }) => {
|
||||
console.log(' ')
|
||||
return []
|
||||
},
|
||||
// db.project.findMany({ where: { forkedFromId: root.id } }),
|
||||
childForks: (_obj, { root }) =>
|
||||
db.project.findMany({ where: { forkedFromId: root.id } }),
|
||||
user: (_obj, { root }: ResolverArgs<ReturnType<typeof project>>) =>
|
||||
db.user.findUnique({ where: { id: root.userId } }),
|
||||
socialCard: (_obj, { root }: ResolverArgs<ReturnType<typeof project>>) =>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"cad": "yarn rw build api && docker-compose --file ./api/src/docker/docker-compose.yml up --build",
|
||||
"cad-r": "yarn rw build api && docker-compose --file ./api/src/docker/docker-compose.yml restart",
|
||||
"cad": "yarn rw build api && zip-it-and-ship-it api/dist/functions/ api/dist/zipball && docker-compose --file ./api/src/docker/docker-compose.yml up --build",
|
||||
"cad-r": "yarn rw build api && zip-it-and-ship-it api/dist/functions/ api/dist/zipball && docker-compose --file ./api/src/docker/docker-compose.yml restart",
|
||||
"aws-emulate": "nodemon ./api/src/docker/aws-emulator.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -30,9 +30,10 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.x <=16.x",
|
||||
"yarn": ">=1.15"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "yarn rw exec seed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,19 @@
|
||||
|
||||
[web]
|
||||
port = 8910
|
||||
apiUrl = "/.netlify/functions"
|
||||
title = 'CadHub'
|
||||
# apiUrl = "/.netlify/functions"
|
||||
apiUrl = "https://uk5gegwopd.execute-api.us-east-2.amazonaws.com/.netlify/functions"
|
||||
includeEnvironmentVariables = [
|
||||
'GOOGLE_ANALYTICS_ID',
|
||||
'CLOUDINARY_API_KEY',
|
||||
'CLOUDINARY_API_SECRET',
|
||||
# 'CLOUDINARY_API_SECRET',
|
||||
'CAD_LAMBDA_BASE_URL',
|
||||
'SENTRY_DSN',
|
||||
'SENTRY_AUTH_TOKEN',
|
||||
'SENTRY_ORG',
|
||||
'SENTRY_PROJECT',
|
||||
'EMAIL_PASSWORD',
|
||||
# 'EMAIL_PASSWORD'
|
||||
]
|
||||
# experimentalFastRefresh = true # this seems to break cascadeStudio
|
||||
[api]
|
||||
|
||||
188
app/serverless.yml
Normal file
188
app/serverless.yml
Normal file
@@ -0,0 +1,188 @@
|
||||
# See the full yml reference at https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/
|
||||
service: cadhubapi
|
||||
|
||||
# Uncomment org and app if you want to integrate your deployment with the Serverless dashboard. See https://www.serverless.com/framework/docs/dashboard/ for more details.
|
||||
# org: your-org
|
||||
# app: your-app
|
||||
|
||||
plugins:
|
||||
- serverless-dotenv-plugin
|
||||
- serverless-binary-cors
|
||||
- serverless-plugin-git-variables
|
||||
|
||||
custom:
|
||||
dotenv:
|
||||
include:
|
||||
- DATABASE_URL_PROD
|
||||
- CLOUDINARY_API_KEY
|
||||
- CLOUDINARY_API_SECRET
|
||||
- EMAIL_PASSWORD
|
||||
- SENTRY_DSN
|
||||
# - # List the environment variables you want to include from your .env file here.
|
||||
|
||||
provider:
|
||||
name: aws
|
||||
lambdaHashingVersion: 20201221
|
||||
runtime: nodejs14.x
|
||||
region: us-east-2 # This is the AWS region where the service will be deployed.
|
||||
httpApi: # HTTP API is used by default. To learn about the available options in API Gateway, see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html
|
||||
cors: true
|
||||
payload: '1.0'
|
||||
stackTags: # Add CloudFormation stack tags here
|
||||
source: serverless
|
||||
name: Redwood Lambda API with HTTP API Gateway
|
||||
tags: # Add service wide tags here
|
||||
name: Redwood Lambda API with HTTP API Gateway
|
||||
ecr:
|
||||
images:
|
||||
# this image is built locally and push to ECR
|
||||
openscadimage:
|
||||
path: ./
|
||||
file: api/src/docker/openscad/Dockerfile
|
||||
cadqueryimage:
|
||||
path: ./
|
||||
file: api/src/docker/cadquery/Dockerfile
|
||||
apiGateway:
|
||||
metrics: true
|
||||
binaryMediaTypes:
|
||||
# we need to allow binary types to be able to send back images and stls, but it would be better to be more specific
|
||||
# ie image/png etc. as */* treats everything as binary including the json body as the input the lambdas
|
||||
# which mean we need to decode the input bode from base64, but the images break with anything other than */* :(
|
||||
- '*/*'
|
||||
|
||||
package:
|
||||
individually: true
|
||||
|
||||
functions:
|
||||
check-user-name:
|
||||
description: check-user-name function deployed on AWS Lambda
|
||||
package:
|
||||
artifact: api/dist/zipball/check-user-name.zip # This is the default location of the zip file generated during the deploy command.
|
||||
memorySize: 1024 # mb
|
||||
timeout: 25 # seconds (max: 29)
|
||||
tags: # Tags for this specific lambda function
|
||||
endpoint: /.netlify/functions/check-user-name
|
||||
# Uncomment this section to add environment variables either from the Serverless dotenv plugin or using Serverless params
|
||||
environment:
|
||||
SENTRY_DSN: ${env:SENTRY_DSN}
|
||||
DATABASE_URL: ${env:DATABASE_URL_PROD}
|
||||
COMMIT_REF: ${git:sha1}
|
||||
CONTEXT: TODO
|
||||
handler: check-user-name.handler
|
||||
events:
|
||||
- httpApi:
|
||||
path: /.netlify/functions/check-user-name
|
||||
method: GET
|
||||
# cors: true
|
||||
- httpApi:
|
||||
path: /.netlify/functions/check-user-name
|
||||
method: POST
|
||||
# cors: true
|
||||
graphql:
|
||||
description: graphql function deployed on AWS Lambda
|
||||
package:
|
||||
artifact: api/dist/zipball/graphql.zip # This is the default location of the zip file generated during the deploy command.
|
||||
memorySize: 1024 # mb
|
||||
timeout: 25 # seconds (max: 29)
|
||||
tags: # Tags for this specific lambda function
|
||||
endpoint: /.netlify/functions/graphql
|
||||
# Uncomment this section to add environment variables either from the Serverless dotenv plugin or using Serverless params
|
||||
environment:
|
||||
CLOUDINARY_API_KEY: ${env:CLOUDINARY_API_KEY}
|
||||
CLOUDINARY_API_SECRET: ${env:CLOUDINARY_API_SECRET}
|
||||
EMAIL_PASSWORD: ${env:EMAIL_PASSWORD}
|
||||
SENTRY_DSN: ${env:SENTRY_DSN}
|
||||
DATABASE_URL: ${env:DATABASE_URL_PROD}
|
||||
COMMIT_REF: ${git:sha1}
|
||||
CONTEXT: TODO
|
||||
# YOUR_FIRST_ENV_VARIABLE: ${env:YOUR_FIRST_ENV_VARIABLE}
|
||||
handler: graphql.handler
|
||||
events:
|
||||
- httpApi:
|
||||
path: /.netlify/functions/graphql
|
||||
method: GET
|
||||
# cors: true
|
||||
- httpApi:
|
||||
path: /.netlify/functions/graphql
|
||||
method: POST
|
||||
# cors: true
|
||||
# identity-signup: # this is netlify specific and is related to go true auth, so we'll continue having that deployed on netlify
|
||||
# description: identity-signup function deployed on AWS Lambda
|
||||
# package:
|
||||
# artifact: api/dist/zipball/identity-signup.zip # This is the default location of the zip file generated during the deploy command.
|
||||
# memorySize: 1024 # mb
|
||||
# timeout: 25 # seconds (max: 29)
|
||||
# tags: # Tags for this specific lambda function
|
||||
# endpoint: /.netlify/functions/identity-signup
|
||||
# # Uncomment this section to add environment variables either from the Serverless dotenv plugin or using Serverless params
|
||||
# # environment:
|
||||
# # YOUR_FIRST_ENV_VARIABLE: ${env:YOUR_FIRST_ENV_VARIABLE}
|
||||
# handler: identity-signup.handler
|
||||
# events:
|
||||
# - httpApi:
|
||||
# path: /.netlify/functions/identity-signup
|
||||
# method: GET
|
||||
# - httpApi:
|
||||
# path: /.netlify/functions/identity-signup
|
||||
# method: POST
|
||||
openscadpreview:
|
||||
image:
|
||||
name: openscadimage
|
||||
command:
|
||||
- openscad.preview
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: openscad/preview
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 25
|
||||
openscadstl:
|
||||
image:
|
||||
name: openscadimage
|
||||
command:
|
||||
- openscad.stl
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: openscad/stl
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 30
|
||||
cadquerystl:
|
||||
image:
|
||||
name: cadqueryimage
|
||||
command:
|
||||
- cadquery.stl
|
||||
entryPoint:
|
||||
- '/entrypoint.sh'
|
||||
events:
|
||||
- http:
|
||||
path: cadquery/stl
|
||||
method: post
|
||||
cors: true
|
||||
timeout: 30
|
||||
|
||||
# this allows browsers to see error responses.
|
||||
resources:
|
||||
Resources:
|
||||
GatewayResponseDefault4XX:
|
||||
Type: 'AWS::ApiGateway::GatewayResponse'
|
||||
Properties:
|
||||
ResponseParameters:
|
||||
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
|
||||
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
|
||||
ResponseType: DEFAULT_4XX
|
||||
RestApiId:
|
||||
Ref: 'ApiGatewayRestApi'
|
||||
GatewayResponseDefault5XX:
|
||||
Type: 'AWS::ApiGateway::GatewayResponse'
|
||||
Properties:
|
||||
ResponseParameters:
|
||||
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
|
||||
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
|
||||
ResponseType: DEFAULT_5XX
|
||||
RestApiId:
|
||||
Ref: 'ApiGatewayRestApi'
|
||||
@@ -6,7 +6,7 @@ module.exports = (config, { env }) => {
|
||||
}
|
||||
})
|
||||
config.module.rules.push({
|
||||
test: /\.(md|jscad\.js|py|scad|curv)$/i,
|
||||
test: /\.(md|jscad\.js|py|scad)$/i,
|
||||
use: 'raw-loader',
|
||||
});
|
||||
return config
|
||||
|
||||
@@ -56,7 +56,6 @@ const Routes = () => {
|
||||
<Route path="/u/{userName}" page={UserPage} name="user" />
|
||||
<Route path="/u/{userName}/{projectTitle}" page={ProjectPage} name="project" />
|
||||
<Route path="/u/{userName}/{projectTitle}/ide" page={IdeProjectPage} name="ide" />
|
||||
<Route path="/u/{userName}/{projectTitle}/embed" page={EmbedProjectPage} name="embed" />
|
||||
<Route path="/u/{userName}/{projectTitle}/social-card" page={SocialCardPage} name="socialCard" />
|
||||
|
||||
<Private unauthenticated="home" role="admin">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'curv' | 'INIT'
|
||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'INIT'
|
||||
|
||||
interface CadPackageConfig {
|
||||
label: string
|
||||
@@ -23,11 +23,6 @@ export const cadPackageConfigs: { [key in CadPackageType]: CadPackageConfig } =
|
||||
buttonClasses: 'bg-ch-purple-500',
|
||||
dotClasses: 'bg-yellow-300',
|
||||
},
|
||||
curv: {
|
||||
label: 'Curv',
|
||||
buttonClasses: 'bg-blue-600',
|
||||
dotClasses: 'bg-green-500',
|
||||
},
|
||||
INIT: {
|
||||
label: '',
|
||||
buttonClasses: '',
|
||||
@@ -54,7 +49,7 @@ const CadPackage = ({
|
||||
<ButtonOrDiv
|
||||
onClick={onClick}
|
||||
className={
|
||||
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30
|
||||
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30
|
||||
${cadPackageConfig?.buttonClasses} ` + className
|
||||
}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,6 @@ const CaptureButtonViewer = ({
|
||||
const threeInstance = React.useRef(null)
|
||||
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
|
||||
const [artifact, artifactSetter] = useState(state?.objectData?.data)
|
||||
const [ideType] = useState(state?.ideType)
|
||||
const [isLoading, isLoadingSetter] = useState(false)
|
||||
const [camera, cameraSetter] = useState<State['camera'] | null>(null)
|
||||
const getThreeInstance = (_threeInstance) => {
|
||||
@@ -34,7 +33,7 @@ const CaptureButtonViewer = ({
|
||||
}
|
||||
const onCameraChange = (camera, isFirstCameraChange) => {
|
||||
const renderPromise =
|
||||
(state.ideType === 'openscad' || state.ideType === 'curv') &&
|
||||
state.ideType === 'openscad' &&
|
||||
requestRenderStateless({
|
||||
state,
|
||||
camera,
|
||||
@@ -71,7 +70,6 @@ const CaptureButtonViewer = ({
|
||||
isLoading={isLoading}
|
||||
camera={camera}
|
||||
isMinimal
|
||||
ideType={ideType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,25 +42,18 @@ const EditableProjectTitle = ({
|
||||
}
|
||||
setNewTitle(target.value.replace(/([^a-zA-Z\d_:])/g, '-').slice(0, 25))
|
||||
}
|
||||
const onKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
updateProject({ variables: { id, input: { title: newTitle } } });
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!inEditMode && (
|
||||
<>
|
||||
/<Link
|
||||
className="underline-hovered"
|
||||
<Link
|
||||
to={routes.project({
|
||||
userName,
|
||||
projectTitle,
|
||||
})}
|
||||
className="pl-4"
|
||||
>
|
||||
{projectTitle}
|
||||
/{projectTitle}
|
||||
</Link>
|
||||
{canEdit && (
|
||||
<button
|
||||
@@ -83,7 +76,6 @@ const EditableProjectTitle = ({
|
||||
value={newTitle}
|
||||
onChange={onTitleChange}
|
||||
ref={inputRef}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={() =>
|
||||
setTimeout(() => {
|
||||
setInEditMode(false)
|
||||
|
||||
@@ -69,8 +69,7 @@ export const makeStlDownloadHandler =
|
||||
} else {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
const specialCadProcess =
|
||||
(ideType === 'openscad' || ideType === 'curv') && 'stl'
|
||||
const specialCadProcess = ideType === 'openscad' && 'stl'
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { useRender } from 'src/components/IdeWrapper/useRender'
|
||||
import { makeStlDownloadHandler, PullTitleFromFirstLine } from 'src/helpers/download_stl'
|
||||
import { makeStlDownloadHandler, PullTitleFromFirstLine } from './helpers'
|
||||
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
|
||||
import { DropdownItem } from './Dropdowns'
|
||||
import { useShortcutsModalContext } from './AllShortcutsModal'
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import Seo from 'src/components/Seo/Seo'
|
||||
import IdeViewer from 'src/components/IdeViewer/IdeViewer'
|
||||
import { useIdeState } from 'src/helpers/hooks/useIdeState'
|
||||
import type { Project } from 'src/components/EmbedProjectCell/EmbedProjectCell'
|
||||
import { IdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
|
||||
interface Props {
|
||||
project?: Project
|
||||
}
|
||||
|
||||
const EmbedProject = ({ project }: Props) => {
|
||||
const [state, thunkDispatch] = useIdeState()
|
||||
const { viewerDomRef, handleViewerSizeUpdate } = use3dViewerResize()
|
||||
|
||||
useEffect(() => {
|
||||
handleViewerSizeUpdate()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen" ref={viewerDomRef} >
|
||||
<IdeContext.Provider value={{ state, thunkDispatch, project }}>
|
||||
<IdeViewer />
|
||||
</IdeContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmbedProject
|
||||
@@ -1,6 +0,0 @@
|
||||
// Define your own mock data here:
|
||||
export const standard = (/* vars, { ctx, req } */) => ({
|
||||
ideProject: {
|
||||
id: 42,
|
||||
},
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Loading, Empty, Success } from './EmbedProjectCell'
|
||||
import { standard } from './EmbedProjectCell.mock'
|
||||
|
||||
export const loading = () => {
|
||||
return Loading ? <Loading /> : null
|
||||
}
|
||||
|
||||
export const empty = () => {
|
||||
return Empty ? <Empty /> : null
|
||||
}
|
||||
|
||||
export const success = () => {
|
||||
return Success ? <Success {...standard()} /> : null
|
||||
}
|
||||
|
||||
export default { title: 'Cells/IdeProjectCell' }
|
||||
@@ -1,21 +0,0 @@
|
||||
import { render, screen } from '@redwoodjs/testing'
|
||||
import { Loading, Empty, Success } from './EmbedProjectCell'
|
||||
import { standard } from './EmbedProjectCell.mock'
|
||||
|
||||
describe('IdeProjectCell', () => {
|
||||
test('Loading renders successfully', () => {
|
||||
render(<Loading />)
|
||||
// Use screen.debug() to see output
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('Empty renders successfully', async () => {
|
||||
render(<Empty />)
|
||||
expect(screen.getByText('Empty')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('Success renders successfully', async () => {
|
||||
render(<Success ideProject={standard().ideProject} />)
|
||||
expect(screen.getByText(/42/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
import { useIdeState } from 'src/helpers/hooks/useIdeState'
|
||||
import { IdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import EmbedViewer from '../EmbedViewer/EmbedViewer'
|
||||
import { QUERY as IdeQuery } from 'src/components/IdeProjectCell'
|
||||
|
||||
export const QUERY = IdeQuery
|
||||
export interface Project {
|
||||
id: string
|
||||
title: string
|
||||
code: string
|
||||
description: string
|
||||
mainImage: string
|
||||
createdAt: string
|
||||
cadPackage: 'openscad' | 'cadquery'
|
||||
user: {
|
||||
id: string
|
||||
userName: string
|
||||
image: string
|
||||
}
|
||||
}
|
||||
|
||||
export const Loading = () => <div>Loading...</div>
|
||||
|
||||
export const Empty = () => <div>Project not found</div>
|
||||
|
||||
interface SaveCodeArgs {
|
||||
input: any
|
||||
id: string
|
||||
isFork: boolean
|
||||
}
|
||||
|
||||
export const Success = ({
|
||||
project,
|
||||
refetch,
|
||||
}: {
|
||||
project: Project
|
||||
refetch: any
|
||||
}) => {
|
||||
const [state, thunkDispatch] = useIdeState()
|
||||
|
||||
|
||||
return (
|
||||
<IdeContext.Provider value={{ state, thunkDispatch, project }}>
|
||||
<EmbedViewer project={project} />
|
||||
</IdeContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useIdeInit } from 'src/components/EncodedUrl/helpers'
|
||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import IdeViewer from 'src/components/IdeViewer/IdeViewer'
|
||||
import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize'
|
||||
import CadPackage from '../CadPackage/CadPackage'
|
||||
import LogoType from '../LogoType/LogoType'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
|
||||
function EmbedViewer() {
|
||||
const { state, project } = useIdeContext()
|
||||
useIdeInit(project?.cadPackage, project?.code || state?.code, "viewer")
|
||||
const { viewerDomRef } = use3dViewerResize()
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col h-screen group" ref={viewerDomRef}>
|
||||
<IdeViewer isMinimal={true} />
|
||||
<div className="absolute top-5 left-5 text-ch-gray-300">
|
||||
<h1 className="mb-4 text-4xl font-normal capitalize ">
|
||||
{project?.title.replace(/-/g, ' ')}
|
||||
</h1>
|
||||
<h2 className="mb-2 transition-opacity duration-100 group-hover:opacity-0">by @{ project?.user?.userName }</h2>
|
||||
<h2 className="transition-opacity duration-100 group-hover:opacity-0">built with <div className="inline-block"><CadPackage cadPackage={project?.cadPackage} className="px-3 py-2"/></div></h2>
|
||||
</div>
|
||||
<div className="absolute grid items-center grid-flow-col-dense gap-2 bottom-5 right-5 text-ch-gray-300">
|
||||
View on <Link className="inline-block" to={routes.project({
|
||||
userName: project?.user?.userName,
|
||||
projectTitle: project?.title.toString(),
|
||||
})}><LogoType className="inline-block" wrappedInLink={true}/></Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmbedViewer
|
||||
@@ -348,10 +348,6 @@ function ChooseYourCharacter() {
|
||||
cadPackage: 'jscad',
|
||||
desc: 'A JavaScript Code-CAD library that will feel familiar to web developers, based on the same tech as OpenSCAD.',
|
||||
},
|
||||
{
|
||||
cadPackage: 'curv',
|
||||
desc: "Curv is a programming language for creating art using mathematics. It's a 2D and 3D geometric modelling tool.",
|
||||
},
|
||||
].map(
|
||||
({
|
||||
cadPackage,
|
||||
|
||||
@@ -18,7 +18,6 @@ const IdeEditor = ({ Loading }) => {
|
||||
cadquery: 'python',
|
||||
openscad: 'cpp',
|
||||
jscad: 'javascript',
|
||||
curv: 'python',
|
||||
INIT: '',
|
||||
}
|
||||
const monaco = useMonaco()
|
||||
|
||||
@@ -102,9 +102,9 @@ export default function IdeHeader({
|
||||
<div className="flex h-full items-center text-gray-300">
|
||||
{project?.id && (
|
||||
<>
|
||||
<span className="h-full grid grid-flow-col-dense items-center gap-2 ml-4">
|
||||
<span className="bg-ch-gray-700 h-full grid grid-flow-col-dense items-center gap-2 px-4">
|
||||
<Gravatar image={project?.user?.image} className="w-10" />
|
||||
<Link
|
||||
className="underline-hovered"
|
||||
to={routes.user({
|
||||
userName: projectOwner,
|
||||
})}
|
||||
@@ -129,7 +129,7 @@ export default function IdeHeader({
|
||||
<TopButton
|
||||
onClick={onClick}
|
||||
name="Save Project Image"
|
||||
className="bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
>
|
||||
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
|
||||
</TopButton>
|
||||
@@ -138,19 +138,19 @@ export default function IdeHeader({
|
||||
)}
|
||||
{!isProfile && (
|
||||
<TopButton
|
||||
className="bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
onClick={handleRender}
|
||||
name={canEdit ? 'Save' : 'Preview'}
|
||||
>
|
||||
<Svg
|
||||
name={canEdit ? 'floppy-disk' : 'photograph'}
|
||||
className="w-6 h-6 text-ch-blue-400"
|
||||
className="w-6 h-6 text-ch-pink-500"
|
||||
/>
|
||||
</TopButton>
|
||||
)}
|
||||
{isProfile && (
|
||||
<TopButton
|
||||
className="bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
routes.ide({
|
||||
@@ -161,7 +161,7 @@ export default function IdeHeader({
|
||||
}
|
||||
name="Editor"
|
||||
>
|
||||
<Svg name="terminal" className="w-6 h-6 text-ch-blue-400" />
|
||||
<Svg name="terminal" className="w-6 h-6 text-ch-pink-500" />
|
||||
</TopButton>
|
||||
)}
|
||||
<Popover className="relative outline-none w-full h-full">
|
||||
@@ -172,11 +172,11 @@ export default function IdeHeader({
|
||||
<TopButton
|
||||
Tag="div"
|
||||
name="Share"
|
||||
className="bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
className=" bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
>
|
||||
<Svg
|
||||
name="share"
|
||||
className="w-6 h-6 text-ch-blue-400 mt-1"
|
||||
className="w-6 h-6 text-ch-purple-500 mt-1"
|
||||
/>
|
||||
</TopButton>
|
||||
</Popover.Button>
|
||||
@@ -221,7 +221,7 @@ export default function IdeHeader({
|
||||
<div className="fixed bg-white w-60 h-10 top-16 right-24 z-10 rounded-md text-sm flex p-2 items-center">
|
||||
<Svg
|
||||
name="exclamation-circle"
|
||||
className="w-8 h-8 mx-2 text-ch-blue-400"
|
||||
className="w-8 h-8 mx-2 text-ch-blue-500"
|
||||
/>{' '}
|
||||
Fork to save your work
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface Project {
|
||||
code: string
|
||||
mainImage: string
|
||||
createdAt: string
|
||||
cadPackage: 'openscad' | 'cadquery' | 'jscad' | 'curv'
|
||||
cadPackage: 'openscad' | 'cadquery'
|
||||
user: {
|
||||
id: string
|
||||
userName: string
|
||||
|
||||
@@ -4,15 +4,12 @@ import { PureIdeViewer } from './PureIdeViewer'
|
||||
|
||||
const IdeViewer = ({
|
||||
handleOwnCamera = false,
|
||||
isMinimal = false,
|
||||
}: {
|
||||
handleOwnCamera?: boolean,
|
||||
isMinimal?: boolean,
|
||||
handleOwnCamera?: boolean
|
||||
}) => {
|
||||
const { state, thunkDispatch } = useIdeContext()
|
||||
const dataType = state.objectData?.type
|
||||
const artifact = state.objectData?.data
|
||||
const ideType = state.ideType
|
||||
|
||||
const onInit = (threeInstance) => {
|
||||
thunkDispatch({ type: 'setThreeInstance', payload: threeInstance })
|
||||
@@ -27,12 +24,7 @@ const IdeViewer = ({
|
||||
})
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (
|
||||
['png', 'INIT'].includes(state?.objectData?.type) &&
|
||||
(ideType === 'openscad' ||
|
||||
state?.objectData?.type === 'INIT' ||
|
||||
!state?.objectData?.type)
|
||||
) {
|
||||
if (['png', 'INIT'].includes(state?.objectData?.type)) {
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
@@ -43,7 +35,7 @@ const IdeViewer = ({
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<PureIdeViewer
|
||||
dataType={dataType}
|
||||
@@ -52,8 +44,6 @@ const IdeViewer = ({
|
||||
onCameraChange={onCameraChange}
|
||||
isLoading={state.isLoading}
|
||||
camera={state?.camera}
|
||||
ideType={ideType}
|
||||
isMinimal={isMinimal}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -169,7 +169,6 @@ export function PureIdeViewer({
|
||||
isMinimal = false,
|
||||
scadRatio = 1,
|
||||
camera,
|
||||
ideType,
|
||||
}: {
|
||||
dataType: 'INIT' | ArtifactTypes
|
||||
artifact: any
|
||||
@@ -179,7 +178,6 @@ export function PureIdeViewer({
|
||||
isMinimal?: boolean
|
||||
scadRatio?: number
|
||||
camera?: State['camera']
|
||||
ideType?: State['ideType']
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [image, setImage] = useState()
|
||||
@@ -212,15 +210,13 @@ export function PureIdeViewer({
|
||||
alt="code-cad preview"
|
||||
id="special"
|
||||
src={URL.createObjectURL(image)}
|
||||
className="w-full h-full"
|
||||
className="h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
|
||||
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
||||
ideType === 'curv' && dataType === 'png' // TODO hide axes while curve doesn't have a controllable camera
|
||||
? 'opacity-0'
|
||||
: !(isDragging || dataType !== 'png')
|
||||
!(isDragging || dataType !== 'png')
|
||||
? 'hover:opacity-50'
|
||||
: 'opacity-100'
|
||||
}`}
|
||||
@@ -230,10 +226,7 @@ export function PureIdeViewer({
|
||||
<Controls
|
||||
onDragStart={() => setIsDragging(true)}
|
||||
onInit={onInit}
|
||||
onCameraChange={(...args) => {
|
||||
onCameraChange(...args)
|
||||
setIsDragging(false)
|
||||
}}
|
||||
onCameraChange={onCameraChange}
|
||||
controlsRef={controlsRef}
|
||||
camera={camera}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,6 @@ interface EditToggleType {
|
||||
onEdit?: React.MouseEventHandler
|
||||
isEditing?: boolean
|
||||
}
|
||||
// small change
|
||||
|
||||
const EditToggle = ({
|
||||
onEdit = () => {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import Svg from 'src/components/Svg'
|
||||
|
||||
export default function LogoType({ className="", wrappedInLink=false }) {
|
||||
return (
|
||||
<ul className={"flex items-center " + className}>
|
||||
<li>
|
||||
{ (wrappedInLink
|
||||
? <Link to={routes.home()}>
|
||||
<div className="ml-2 overflow-hidden rounded-full">
|
||||
<Svg className="w-10" name="favicon" />
|
||||
</div>
|
||||
</Link>
|
||||
: <div>
|
||||
<div className="ml-2 overflow-hidden rounded-full">
|
||||
<Svg className="w-10" name="favicon" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip title="Very alpha, there's lots of work todo">
|
||||
<div className="flex ml-4">
|
||||
{/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */}
|
||||
<h2
|
||||
className="py-1 text-2xl text-indigo-300 md:text-5xl font-ropa-sans md:tracking-wider"
|
||||
style={{ letterSpacing: '0.3em' }}
|
||||
>
|
||||
CadHub
|
||||
</h2>
|
||||
<div
|
||||
className="hidden text-sm font-bold text-pink-400 font-ropa-sans md:block"
|
||||
style={{ paddingBottom: '2rem', marginLeft: '-1.8rem' }}
|
||||
>
|
||||
pre-alpha
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -95,13 +95,6 @@ const menuOptions: {
|
||||
dotClasses: 'bg-yellow-300',
|
||||
ideType: 'jscad',
|
||||
},
|
||||
{
|
||||
name: 'Curv',
|
||||
sub: 'alpha ',
|
||||
bgClasses: 'bg-blue-600',
|
||||
dotClasses: 'bg-green-500',
|
||||
ideType: 'curv',
|
||||
},
|
||||
]
|
||||
|
||||
const NavPlusButton: React.FC = () => {
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import OutBound from 'src/components/OutBound/OutBound'
|
||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
|
||||
const StaticImageMessage = () => {
|
||||
const OpenscadStaticImageMessage = () => {
|
||||
const { state } = useIdeContext()
|
||||
if ((state.ideType !== 'openscad' && state.ideType !== 'curv') || state.objectData?.type !== 'png') {
|
||||
if (state.ideType !== 'openscad' || state.objectData?.type !== 'png') {
|
||||
return null
|
||||
}
|
||||
return state.ideType === 'openscad' ?
|
||||
return (
|
||||
<OutBound
|
||||
to="https://learn.cadhub.xyz/docs/general-cadhub/openscad-previews"
|
||||
className="text-ch-gray-300 border-ch-gray-300 rounded-md mr-12 px-2 py-1 text-xs"
|
||||
>
|
||||
Why reload each camera move?
|
||||
</OutBound>
|
||||
: <div className="text-ch-gray-300 border-ch-gray-300 rounded-md mr-12 px-2 py-1 text-xs">
|
||||
Alpha Curv integration, no camera support currently.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StaticImageMessage
|
||||
export default OpenscadStaticImageMessage
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MouseEventHandler, useContext } from 'react'
|
||||
import { MosaicWindowContext } from 'react-mosaic-component'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import StaticImageMessage from 'src/components/StaticImageMessage/StaticImageMessage'
|
||||
import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessage/OpenscadStaticImageMessage'
|
||||
|
||||
const PanelToolbar = ({
|
||||
panelName,
|
||||
@@ -19,7 +19,7 @@ const PanelToolbar = ({
|
||||
<div className="absolute inset-x-0 top-0 h-10 bg-gradient-to-b from-ch-gray-800 to-transparent" />
|
||||
)}
|
||||
<div className="absolute top-0 right-0 flex items-center h-9">
|
||||
{panelName === 'Viewer' && <StaticImageMessage />}
|
||||
{panelName === 'Viewer' && <OpenscadStaticImageMessage />}
|
||||
<button
|
||||
className={
|
||||
'bg-ch-gray-760 text-ch-gray-300 px-3 rounded-bl-lg h-full ' +
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useMutation } from '@redwoodjs/web'
|
||||
import { toast } from '@redwoodjs/web/toast'
|
||||
import { navigate, routes } from '@redwoodjs/router'
|
||||
import { useAuth } from '@redwoodjs/auth'
|
||||
import { makeStlDownloadHandler } from 'src/helpers/download_stl'
|
||||
import { useIdeState } from 'src/helpers/hooks/useIdeState'
|
||||
import { IdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import { CREATE_PROJECT_MUTATION } from 'src/components/NavPlusButton/NavPlusButton'
|
||||
@@ -193,15 +192,6 @@ export const Success = ({ userProject, refetch }) => {
|
||||
},
|
||||
})
|
||||
|
||||
const onStlDownload = makeStlDownloadHandler({
|
||||
type: state.objectData?.type,
|
||||
ideType: state.ideType,
|
||||
geometry: state.objectData?.data,
|
||||
quality: state.objectData?.quality,
|
||||
fileName: `${userProject.Project?.title }.stl`,
|
||||
thunkDispatch,
|
||||
})
|
||||
|
||||
return (
|
||||
<IdeContext.Provider
|
||||
value={{
|
||||
@@ -223,7 +213,6 @@ export const Success = ({ userProject, refetch }) => {
|
||||
onDelete={onDelete}
|
||||
onReaction={onReaction}
|
||||
onComment={onComment}
|
||||
onStlDownload={onStlDownload}
|
||||
/>
|
||||
</IdeContext.Provider>
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ import CadPackage from 'src/components/CadPackage/CadPackage'
|
||||
import Gravatar from 'src/components/Gravatar/Gravatar'
|
||||
import { useIdeInit } from 'src/components/EncodedUrl/helpers'
|
||||
import ProfileViewer from '../ProfileViewer/ProfileViewer'
|
||||
import StaticImageMessage from 'src/components/StaticImageMessage/StaticImageMessage'
|
||||
import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessage/OpenscadStaticImageMessage'
|
||||
import KeyValue from 'src/components/KeyValue/KeyValue'
|
||||
|
||||
const ProjectProfile = ({
|
||||
@@ -25,7 +25,6 @@ const ProjectProfile = ({
|
||||
onDelete,
|
||||
onReaction,
|
||||
onComment,
|
||||
onStlDownload,
|
||||
}) => {
|
||||
const [comment, setComment] = useState('')
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
@@ -82,7 +81,7 @@ const ProjectProfile = ({
|
||||
<div className="md:col-start-2 w-full min-h-md relative">
|
||||
<ProfileViewer />
|
||||
<div className="absolute right-0 top-0">
|
||||
<StaticImageMessage />
|
||||
<OpenscadStaticImageMessage />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -92,25 +91,12 @@ const ProjectProfile = ({
|
||||
<h3 className="text-5xl capitalize text-ch-gray-300">
|
||||
{project?.title.replace(/-/g, ' ')}
|
||||
</h3>
|
||||
<div className="flex items-center text-gray-100 flex-wrap">
|
||||
<div className="flex flex-grow items-center">
|
||||
<span className="pr-4">Built with</span>
|
||||
<CadPackage
|
||||
cadPackage={project?.cadPackage}
|
||||
className="px-3 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className={getActiveClasses(
|
||||
'ml-3 hover:bg-opacity-100 bg-ch-pink-800 bg-opacity-30 mt-4 mb-3 text-ch-gray-300',
|
||||
{ 'bg-indigo-200': currentUser }
|
||||
)}
|
||||
shouldAnimateHover
|
||||
iconName={'document-download'}
|
||||
onClick={onStlDownload}
|
||||
>
|
||||
Download STL
|
||||
</Button>
|
||||
<div className="flex items-center text-gray-100">
|
||||
<span className="pr-4">Built with</span>
|
||||
<CadPackage
|
||||
cadPackage={project?.cadPackage}
|
||||
className="px-3 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
{(project?.description || hasPermissionToEdit) && (
|
||||
<KeyValue
|
||||
|
||||
@@ -139,21 +139,6 @@ const Svg = ({
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
'document-download': (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
'dots-vertical': (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Camera } from 'src/helpers/hooks/useIdeState'
|
||||
|
||||
export const lambdaBaseURL =
|
||||
process.env.CAD_LAMBDA_BASE_URL ||
|
||||
'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2/'
|
||||
'https://9inkvuvxz5.execute-api.us-east-2.amazonaws.com/dev'
|
||||
|
||||
export const stlToGeometry = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import {
|
||||
lambdaBaseURL,
|
||||
stlToGeometry,
|
||||
createHealthyResponse,
|
||||
createUnhealthyResponse,
|
||||
timeoutErrorMessage,
|
||||
RenderArgs,
|
||||
splitGziped,
|
||||
} from '../common'
|
||||
|
||||
export const render = async ({ code, settings }: RenderArgs) => {
|
||||
const pixelRatio = window.devicePixelRatio || 1
|
||||
const size = {
|
||||
x: Math.round(settings.viewerSize?.width * pixelRatio),
|
||||
y: Math.round(settings.viewerSize?.height * pixelRatio),
|
||||
}
|
||||
const body = JSON.stringify({
|
||||
settings: {
|
||||
size,
|
||||
},
|
||||
file: code,
|
||||
})
|
||||
try {
|
||||
const response = await fetch(lambdaBaseURL + '/curv/preview', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
})
|
||||
if (response.status === 400) {
|
||||
const { error } = await response.json()
|
||||
const cleanedErrorMessage = cleanError(error)
|
||||
return createUnhealthyResponse(new Date(), cleanedErrorMessage)
|
||||
}
|
||||
if (response.status === 502) {
|
||||
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
const text = await new Response(blob).text()
|
||||
const { consoleMessage, type } = splitGziped(text)
|
||||
return createHealthyResponse({
|
||||
type: type !== 'stl' ? 'png' : 'geometry',
|
||||
data:
|
||||
type !== 'stl'
|
||||
? blob
|
||||
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
||||
consoleMessage,
|
||||
date: new Date(),
|
||||
})
|
||||
} catch (e) {
|
||||
return createUnhealthyResponse(new Date())
|
||||
}
|
||||
}
|
||||
|
||||
export const stl = async ({ code /*settings*/ }: RenderArgs) => {
|
||||
const body = JSON.stringify({
|
||||
settings: {},
|
||||
file: code,
|
||||
})
|
||||
try {
|
||||
const response = await fetch(lambdaBaseURL + '/curv/stl', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
})
|
||||
if (response.status === 400) {
|
||||
const { error } = await response.json()
|
||||
const cleanedErrorMessage = cleanError(error)
|
||||
return createUnhealthyResponse(new Date(), cleanedErrorMessage)
|
||||
}
|
||||
if (response.status === 502) {
|
||||
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
const text = await new Response(blob).text()
|
||||
const { consoleMessage, type } = splitGziped(text)
|
||||
return createHealthyResponse({
|
||||
type: type !== 'stl' ? 'png' : 'geometry',
|
||||
data:
|
||||
type !== 'stl'
|
||||
? blob
|
||||
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
||||
consoleMessage,
|
||||
date: new Date(),
|
||||
})
|
||||
} catch (e) {
|
||||
return createUnhealthyResponse(new Date())
|
||||
}
|
||||
}
|
||||
|
||||
const curv = {
|
||||
render,
|
||||
stl,
|
||||
}
|
||||
|
||||
export default curv
|
||||
|
||||
function cleanError(error) {
|
||||
return error.replace(/["|']\/tmp\/.+\/main.curv["|']/g, "'main.curv'")
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
let
|
||||
N = 5;
|
||||
C = red;
|
||||
Twists = 6;
|
||||
in
|
||||
box [1,1,N]
|
||||
>> colour C
|
||||
>> twist (Twists*90*deg/N)
|
||||
>> rotate {angle: 90*deg, axis: Y_axis}
|
||||
>> bend{}
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
title: Curv
|
||||
Written with: [Domain-Specific Language](https://martinfowler.com/dsl.html)
|
||||
Kernal type: Signed distance functions
|
||||
Maintained by: [Doug Moen and contributors](https://github.com/curv/curv/graphs/contributors)
|
||||
Documentation: [curv3d.org](https://curv3d.org)
|
||||
---
|
||||
|
||||
Curv is a programming language for creating art using mathematics. It's a 2D and 3D geometric modelling tool that supports full colour, animation and 3D printing.
|
||||
|
||||
### [Examples](https://github.com/curv3d/curv/tree/master/examples)
|
||||
|
||||
- [Flog spiral](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Flog_spiral.curv)
|
||||
- [Shreks donut](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Fshreks_donut.curv)
|
||||
- [Wood grain](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Ffinial.curv)
|
||||
@@ -13,22 +13,16 @@ import jscad from './jsCad/jsCadController'
|
||||
import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md'
|
||||
import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad.js'
|
||||
|
||||
import curv from './curv/curvController'
|
||||
import curvGuide from 'src/helpers/cadPackages/curv/userGuide.md'
|
||||
import curvInitialCode from 'src/helpers/cadPackages/curv/initialCode.curv'
|
||||
|
||||
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
||||
openscad,
|
||||
cadquery,
|
||||
jscad,
|
||||
curv,
|
||||
}
|
||||
|
||||
export const initGuideMap: { [key in CadPackageType]: string } = {
|
||||
openscad: openScadGuide,
|
||||
cadquery: cadQueryGuide,
|
||||
jscad: jsCadGuide,
|
||||
curv: curvGuide,
|
||||
INIT: '',
|
||||
}
|
||||
|
||||
@@ -36,6 +30,5 @@ export const initCodeMap: { [key in CadPackageType]: string } = {
|
||||
openscad: openScadInitialCode,
|
||||
cadquery: cadQueryInitialCode,
|
||||
jscad: jsCadInitialCode,
|
||||
curv: curvInitialCode,
|
||||
INIT: '',
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import TheWorker from 'worker-loader!./jscadWorker'
|
||||
|
||||
const materials = {
|
||||
mesh: {
|
||||
def: new MeshPhongMaterial({ color: 0x13579d, flatShading: true }),
|
||||
def: new MeshPhongMaterial({ color: 0x0084d1, flatShading: true }),
|
||||
material: (params) => new MeshPhongMaterial(params),
|
||||
},
|
||||
line: {
|
||||
|
||||
@@ -8,7 +8,6 @@ type JscadTypeNames =
|
||||
| 'group'
|
||||
| 'text'
|
||||
| 'int'
|
||||
| 'float'
|
||||
| 'number'
|
||||
| 'slider'
|
||||
| 'email'
|
||||
@@ -38,7 +37,7 @@ interface JscadTextParam extends JscadParamBase {
|
||||
maxLength: number
|
||||
}
|
||||
interface JscadIntNumberSliderParam extends JscadParamBase {
|
||||
type: 'int' | 'number' | 'float' | 'slider'
|
||||
type: 'int' | 'number' | 'slider'
|
||||
initial: number
|
||||
min?: number
|
||||
max?: number
|
||||
@@ -94,7 +93,6 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
|
||||
switch (param.type) {
|
||||
case 'slider':
|
||||
case 'number':
|
||||
case 'float':
|
||||
case 'int':
|
||||
return {
|
||||
type: 'number',
|
||||
|
||||
@@ -315,12 +315,9 @@ function parseDef(code, line) {
|
||||
}
|
||||
|
||||
const makeScriptWorker = ({ callback, convertToSolids }) => {
|
||||
let onInit, main, scriptStats, entities, lastParamsDef
|
||||
let onInit, main, scriptStats, entities
|
||||
|
||||
function runMain(params = {}) {
|
||||
if(lastParamsDef) lastParamsDef.forEach(def=>{
|
||||
if(!(def.name in params) && 'initial' in def) params[def.name] = def.initial
|
||||
})
|
||||
let time = Date.now()
|
||||
let solids
|
||||
const transfer = []
|
||||
@@ -400,11 +397,10 @@ const makeScriptWorker = ({ callback, convertToSolids }) => {
|
||||
if (idx === -1) {
|
||||
paramsDef.push(p)
|
||||
} else {
|
||||
paramsDef[idx] = p
|
||||
paramsDef.splice(idx, 1, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
lastParamsDef = paramsDef
|
||||
callback({
|
||||
action: 'parameterDefinitions',
|
||||
worker: 'main',
|
||||
|
||||
@@ -144,6 +144,3 @@ label {
|
||||
input.error, textarea.error {
|
||||
border: 1px solid red;
|
||||
}
|
||||
a.underline-hovered:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { Link, routes, navigate } from '@redwoodjs/router'
|
||||
import { useAuth } from '@redwoodjs/auth'
|
||||
import { Toaster, toast } from '@redwoodjs/web/toast'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { getActiveClasses } from 'get-active-classes'
|
||||
import Footer from 'src/components/Footer'
|
||||
@@ -11,11 +12,11 @@ import NavPlusButton from 'src/components/NavPlusButton'
|
||||
import ReactGA from 'react-ga'
|
||||
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
||||
|
||||
import Svg from 'src/components/Svg'
|
||||
import { ImageFallback } from 'src/components/ImageUploader'
|
||||
import useUser from 'src/helpers/hooks/useUser'
|
||||
import './MainLayout.css'
|
||||
import RecentProjectsCell from 'src/components/RecentProjectsCell'
|
||||
import LogoType from 'src/components/LogoType'
|
||||
|
||||
let previousSubmission = ''
|
||||
|
||||
@@ -71,12 +72,39 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
||||
}, [hash, client])
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col h-full overflow-x-hidden overflow-y-scroll ch-scrollbar"
|
||||
className="h-full flex flex-col ch-scrollbar overflow-y-scroll overflow-x-hidden"
|
||||
style={{ perspective: '1px', perspectiveOrigin: 'top center' }}
|
||||
>
|
||||
<header id="cadhub-main-header">
|
||||
<nav className="flex justify-between h-16 sm:px-4 bg-ch-gray-900">
|
||||
<LogoType />
|
||||
<ul className="flex items-center">
|
||||
<li>
|
||||
<Link to={routes.home()}>
|
||||
<div className="rounded-full overflow-hidden ml-2">
|
||||
<Svg className="w-10" name="favicon" />
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip title="Very alpha, there's lots of work todo">
|
||||
<div className="ml-4 flex">
|
||||
{/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */}
|
||||
<h2
|
||||
className="text-indigo-300 text-2xl md:text-5xl font-ropa-sans py-1 md:tracking-wider"
|
||||
style={{ letterSpacing: '0.3em' }}
|
||||
>
|
||||
CadHub
|
||||
</h2>
|
||||
<div
|
||||
className="text-pink-400 text-sm font-bold font-ropa-sans hidden md:block"
|
||||
style={{ paddingBottom: '2rem', marginLeft: '-1.8rem' }}
|
||||
>
|
||||
pre-alpha
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="flex items-center">
|
||||
<li
|
||||
className={getActiveClasses(
|
||||
@@ -86,29 +114,29 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
||||
<NavPlusButton />
|
||||
</li>
|
||||
{isAuthenticated ? (
|
||||
<li className="w-10 h-10">
|
||||
<Popover className="relative w-full h-full outline-none">
|
||||
<li className="h-10 w-10">
|
||||
<Popover className="relative outline-none w-full h-full">
|
||||
<Popover.Button
|
||||
disabled={!isAuthenticated || !currentUser}
|
||||
className="w-full h-full border-2 rounded-full outline-none border-ch-gray-400"
|
||||
className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full"
|
||||
>
|
||||
{!loading && (
|
||||
<ImageFallback
|
||||
width={80}
|
||||
className="object-cover rounded-full"
|
||||
className="rounded-full object-cover"
|
||||
imageId={user?.image}
|
||||
/>
|
||||
)}
|
||||
</Popover.Button>
|
||||
{currentUser && (
|
||||
<Popover.Panel className="absolute right-0 z-10 w-48 px-3 py-2 mt-4 overflow-hidden rounded shadow-md bg-ch-gray-700 text-ch-gray-300">
|
||||
<Popover.Panel className="w-48 absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
||||
<Link to={routes.user({ userName: user?.userName })}>
|
||||
<p className="my-2 text-sm leading-4 text-ch-blue-400 font-fira-code">
|
||||
<p className="my-2 text-ch-blue-400 font-fira-code leading-4 text-sm">
|
||||
Hello {user?.name}
|
||||
</p>
|
||||
</Link>
|
||||
<Link
|
||||
className="block my-2 hover:text-ch-pink-300"
|
||||
className="my-2 block hover:text-ch-pink-300"
|
||||
to={routes.user({ userName: user?.userName })}
|
||||
>
|
||||
<div>View Your Profile</div>
|
||||
@@ -121,7 +149,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
||||
Logout
|
||||
</a>
|
||||
<hr className="my-4" />
|
||||
<p className="text-sm leading-4 text-ch-blue-400 font-fira-code">
|
||||
<p className="text-ch-blue-400 font-fira-code leading-4 text-sm">
|
||||
Recent Projects
|
||||
</p>
|
||||
<RecentProjectsCell userName={user?.userName} />
|
||||
@@ -133,7 +161,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
className="px-2 py-2 mr-1 border-2 rounded-full text-ch-gray-300 sm:mr-2 sm:px-4 border-ch-gray-400 hover:bg-ch-gray-600"
|
||||
className="text-ch-gray-300 mr-1 sm:mr-2 px-2 sm:px-4 py-2 border-2 border-ch-gray-400 rounded-full hover:bg-ch-gray-600"
|
||||
onClick={recordedLogin}
|
||||
>
|
||||
Sign In/Up
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import EmbedProjectPage from './EmbedProjectPage'
|
||||
|
||||
describe('EmbedProjectPage', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<EmbedProjectPage />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
import EmbedProjectCell from 'src/components/EmbedProjectCell'
|
||||
|
||||
const EmbedProjectPage = ({ userName, projectTitle }) => {
|
||||
return (
|
||||
<>
|
||||
<EmbedProjectCell userName={userName} projectTitle={projectTitle} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmbedProjectPage
|
||||
3763
app/yarn.lock
3763
app/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ It's software that allows you to define 3D CAD models with code. It's a niche po
|
||||
|
||||
I recommend reading through the entire list below to see if one chimes with you and your needs, beyond that I can make the following recommendation and points:
|
||||
<!--truncate-->
|
||||
- My main recommendation is to use one of the packages that uses a B-rep kernal (and for opensource tools that means OpenCascade, a mature C++ CAD library). Packages that do so are CadQuery, CascadeStudio, DeclaraCAD and pythonOCC. My reasons for recommending these are as follows:
|
||||
- My main recommendation is to use one of the packages that uses a B-rep kernal (and for opensounce tools that means OpenCascade, a mature C++ CAD library). Packages that do so are CadQuery, CascadeStudio, DeclaraCAD and pythonOCC. My reasons for recommending these are as follows:
|
||||
- Most of Code-CAD tools are plagued with a CSG mindset (that is unions, subtractions and intersections of primitive shapes; cubes spheres etc). This is an inherently limited paradigm (one simple example of this is how internal fillets, which are important for reducing stress concentrations in parts, become very difficult). While CadQuery, CascadeStudio, DeclaraCAD and pythonOCC still offer CSG functionality, you're also able to move beyond.
|
||||
- OpenCascade uses a B-rep (boundary representation) kernel, In my opinion, this means you'll be learning a future-proof tool that won't limit the types of applications you can model for, which is likely the case for mesh kernels, which will cause trouble in for some applications like optics and injection moulding.
|
||||
|
||||
@@ -206,20 +206,6 @@ Python-based, Also uses [OpenCascade](https://github.com/tpaviot/oce).
|
||||
|
||||
Another project inspired by OpenSCAD. The author considers key differences to be procedural vs functional programming language style, (i.e variables can be modified) and the use of arbitrary precision arithmetic throughout (meaning there are no unexpected double/float rounding errors). There is a handy [feature matrix](https://github.com/GilesBathgate/RapCAD/blob/master/doc/feature_matrix.asciidoc) between RapCAD, OpenSCAD and ImplicitCad.
|
||||
|
||||
### [replicad](https://replicad.xyz)
|
||||
- [Repo](https://github.com/sgenoud/replicad)
|
||||
- [Community](https://github.com/sgenoud/replicad/discussions)
|
||||
- [Docs](https://replicad.xyz/docs/intro)
|
||||
- License: AGPL
|
||||
- [Online editor](https://studio.replicad.xyz/visualiser)
|
||||
|
||||
|
||||
A library to build browser based 3D models with code. Exposes an API inspired by
|
||||
[CadQuery](https://cadquery.readthedocs.io/en/latest/intro.html), written to
|
||||
run in the browser. Replicad is a javascript wrapper on top of
|
||||
[opencascade.js](https://github.com/donalffons/opencascade.js/)
|
||||
|
||||
|
||||
### [scad-clj](https://github.com/farrellm/scad-clj)
|
||||
- [Repo](https://github.com/farrellm/scad-clj)
|
||||
- ~~Community~~
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
slug: ux-studies-intro
|
||||
title: "GUI-CAD UX studies: introduction"
|
||||
author: Frank Noirot
|
||||
author_title: CadHub Core Team
|
||||
author_url: https://github.com/franknoirot
|
||||
author_image_url: https://avatars.githubusercontent.com/u/23481541?v=4
|
||||
tags: []
|
||||
---
|
||||
|
||||
import Image from '@theme/IdealImage';
|
||||
|
||||
import ivanSutherland from '../static/img/blog/ux-case-studies-intro/ivan-sutherland-sketchpad.jpg';
|
||||
|
||||
I'm helping CadHub out by designing the interfaces for the [new editor](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/CadHub?node-id=1114%3A1608), [project viewer](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/?node-id=1046%3A0), and [more](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/?node-id=1150%3A1618). Right now we're focused on getting the Code-CAD user experience perfected so that users can try out all the great Code-CAD packages out there in a simple and sharable way. But we think that the future of Code-CAD will pull UX lessons from traditional, GUI-based CAD systems. So I'll be taking a look at the history and UX of some of today's CAD tools to see how we might bring them along with the Code-CAD evolution.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Why GUIs aren't enough anymore
|
||||
|
||||
As others like [Jessie Frazelle](https://medium.com/embedded-ventures/mechanical-cad-yesterday-today-and-tomorrow-981cef7e06b1) have pointed out, the history of CAD software has been focused primarily on using software to emulate the frictionless user experiences of sketching and modelling by hand. That paradigm lead most of the major tools to build GUI-based systems, as they correctly assumed at the the time that the GUI offered an interface that could be understood by people in the industry. Decades have passed and the same assumption still forms the foundation of the paradigm, but could these assumptions have fundamentally changed?
|
||||
|
||||
<Image img={ivanSutherland} alt="Ivan Sutherland's Sketchpad program from the 1960's, a man using a pen-like tool on a screen to manipulate a 2D model, considered the first CAD program." className="mb-8 bg-contain rounded-md overflow-hidden max-w-lg mx-auto" />
|
||||
|
||||
It's hard to understate how much of a sea change web development has brought to technical culture. In the past decade the web technologies of HTML, CSS, and especially JavaScript have trained a large part of technical workers to think not in terms of software packages, but in terms of the technologies and languages that are used to construct them, because as a culture we have become accustomed to the idea that there is always an API powering whatever tool we're using. Technical users of course still want seamless GUI user experiences on platforms, but increasingly they also want the ability to get under the hood and use the APIs that power whatever tool or platform they're on. This trend is evident in the rise of API-first services like [Stripe](https://stripe.com) and monolith-fracturing trends like [JAMstack web development](https://jamstack.org).
|
||||
|
||||
With Code-CAD, we are putting a spotlight on this sea change in user expectations, and putting out a call to action for people to start creating experiences for this web-native, language-comfortable audience of CAD users. With CadHub, we're building a showcase for the great Code-CAD packages like [CadQuery](https://cadquery.readthedocs.io/en/latest/) and [OpenSCAD](https://openscad.org/) that have been under development by early adopters for years.
|
||||
|
||||
## A Gooey Hegelian Dialectic
|
||||
|
||||
Okay, so we in the Code-CAD community are a bunch of developers who want more interfaces from our CAD programs. We're comfortable with programming languages and APIs and we want access to them in addition to the GUIs that CAD has traditionally provided. That's all great, but then why isn't Code-CAD mainstream right now?
|
||||
|
||||
For one, the process of building a robust, text-first approach to so visual an activity is, as [Kurt has written about](/blog/right-level-of-abstraction) on this blog, incredibly difficult. But the Code-CAD community has been doing the monumental work of developing clean, expressive APIs for modelling. That important work is what we want to showcase and make more accessible with CadHub. But we think it's only half of the equation for creating the next step in CAD.
|
||||
|
||||
There are myriads of thorny user experience problems that have been solved by the dominant CAD packages of today. They are amazing pieces of software that users know and love. And all those clever UX solutions were created in the design space of GUIs. If Code-CAD as a paradigm is going to become the new normal for computer-aided design, we need to understand and address all the innovation that GUI-CAD has brought to design, and translate them into Code-CAD. We need to find a gooey-code synthesis. As previously stated, this new generation of users *still want seamless GUI experiences*. Code-CAD needs to provide a way of switching seamlessly between "Application Programming" and "Graphical User" interfaces.
|
||||
|
||||
## Already under construction
|
||||
|
||||
I'll try to explore that design space with a few brief case studies on UX that I love from existing CAD and 3D modelling software. I'm looking for key experiences that help empower designers, how they operate in the GUI-CAD paradigm, and how Code-CAD might provide code-based synonyms of these GUI experiences.
|
||||
|
||||
But I want to mention that work is already being done on this front. Jeremy Wright of the CadQuery team is building [Semblage](https://semblage.7bindustries.com/en/latest/), a GUI-code hybrid built with CadQuery and the Godot gaming engine. [BuildBee](https://makecode.buildbee.com/) lets users switch between Scratch-like block interface and JavaScript code for making models. Blender provides an [excellent Python API](https://docs.blender.org/api/current/index.html) for almost all of its incredible functionality, and there a dozen other projects pushing things forward while we look to the present and the past for more inspiration.
|
||||
|
||||
Our first stop will be the timeline feature of AutoDesk Fusion360, which is a clever way to make the order of operations in modelling intuitive. Stay tuned for this post and more in the coming weeks, check out our work on [GitHub](https://github.com/Irev-Dev/cadhub/discussions/404) and [Figma](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/?node-id=633%3A0), sign up for [Kurt's newsletter](https://kurthutten.com/), and join [our Discord](https://discord.gg/sFYJyEJ6) to get plugged into our ongoing discussions about the future of Code-CAD.
|
||||
Reference in New Issue
Block a user