Merge pull request #399 from Irev-Dev/main

Release 4th July 2021
This commit was merged in pull request #399.
This commit is contained in:
Kurt Hutten
2021-07-04 20:50:36 +10:00
committed by GitHub
19 changed files with 194 additions and 72 deletions

View File

@@ -37,6 +37,8 @@ yarn rw prisma migrate dev
yarn rw prisma db seed yarn rw prisma db seed
``` ```
p.s. `yarn rw prisma studio` spins up an app to inspect the db
### Fire up dev ### Fire up dev
```terminal ```terminal
yarn rw dev yarn rw dev

View File

@@ -22,7 +22,7 @@ CLOUDINARY_API_KEY=476712943135152
# EMAIL_PASSWORD=abc123 # EMAIL_PASSWORD=abc123
CAD_LAMBDA_BASE_URL="http://localhost:8080" # CAD_LAMBDA_BASE_URL="http://localhost:8080"
# sentry # sentry
GITHUB_ASSIST_APP_ID=23342 GITHUB_ASSIST_APP_ID=23342

View File

@@ -7,7 +7,14 @@ module.exports.runCQ = async ({
} = {}) => { } = {}) => {
const tempFile = await makeFile(file, '.py', nanoid) const tempFile = await makeFile(file, '.py', nanoid)
const fullPath = `/tmp/${tempFile}/output.stl` const fullPath = `/tmp/${tempFile}/output.stl`
const command = `cq-cli/cq-cli --codec stl --infile /tmp/${tempFile}/main.py --outfile ${fullPath} --outputopts "deflection:${deflection};angularDeflection:${deflection};"` const command = [
`cq-cli/cq-cli`,
`--codec stl`,
`--infile /tmp/${tempFile}/main.py`,
`--outfile ${fullPath}`,
`--outputopts "deflection:${deflection};angularDeflection:${deflection};"`,
`&& gzip ${fullPath}`,
].join(' ')
console.log('command', command) console.log('command', command)
try { try {

View File

@@ -117,7 +117,7 @@ async function storeAssetAndReturnUrl({
let buffer let buffer
try { try {
buffer = await readFile(fullPath) buffer = await readFile(`${fullPath}.gz`)
} catch (e) { } catch (e) {
console.log('read file error', e) console.log('read file error', e)
const response = { const response = {
@@ -134,6 +134,8 @@ async function storeAssetAndReturnUrl({
Key: key, Key: key,
Body: buffer, Body: buffer,
CacheControl: `max-age=${FiveDays}`, // browser caching to stop downloads of the same part CacheControl: `max-age=${FiveDays}`, // browser caching to stop downloads of the same part
ContentType: 'text/stl',
ContentEncoding: 'gzip',
Metadata: putConsoleMessageInMetadata(consoleMessage), Metadata: putConsoleMessageInMetadata(consoleMessage),
}) })
.promise() .promise()

View File

@@ -1,6 +1,8 @@
const { makeFile, runCommand } = require('../common/utils') const { makeFile, runCommand } = require('../common/utils')
const { nanoid } = require('nanoid') const { nanoid } = require('nanoid')
const OPENSCAD_COMMON = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad`
/** Removes our generated/hash filename with just "main.scad", so that it's a nice message in the IDE */ /** Removes our generated/hash filename with just "main.scad", so that it's a nice message in the IDE */
const cleanOpenScadError = (error) => const cleanOpenScadError = (error) =>
error.replace(/["|']\/tmp\/.+\/main.scad["|']/g, "'main.scad'") error.replace(/["|']\/tmp\/.+\/main.scad["|']/g, "'main.scad'")
@@ -21,7 +23,15 @@ module.exports.runScad = async ({
const { x: px, y: py, z: pz } = position const { x: px, y: py, z: pz } = position
const cameraArg = `--camera=${px},${py},${pz},${rx},${ry},${rz},${dist}` const cameraArg = `--camera=${px},${py},${pz},${rx},${ry},${rz},${dist}`
const fullPath = `/tmp/${tempFile}/output.png` const fullPath = `/tmp/${tempFile}/output.png`
const command = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad -o ${fullPath} ${cameraArg} --imgsize=${x},${y} --colorscheme CadHub /tmp/${tempFile}/main.scad` const command = [
OPENSCAD_COMMON,
`-o ${fullPath}`,
cameraArg,
`--imgsize=${x},${y}`,
`--colorscheme CadHub`,
`/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ')
console.log('command', command) console.log('command', command)
try { try {
@@ -36,12 +46,17 @@ module.exports.runScad = async ({
module.exports.stlExport = async ({ file } = {}) => { module.exports.stlExport = async ({ file } = {}) => {
const tempFile = await makeFile(file, '.scad', nanoid) const tempFile = await makeFile(file, '.scad', nanoid)
const fullPath = `/tmp/${tempFile}/output.stl` const fullPath = `/tmp/${tempFile}/output.stl`
const command = [
OPENSCAD_COMMON,
`--export-format=binstl`,
`-o ${fullPath}`,
`/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ')
try { try {
const consoleMessage = await runCommand( // lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
`xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad -o ${fullPath} /tmp/${tempFile}/main.scad`, const consoleMessage = await runCommand(command, 60000)
60000 // lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
)
return { consoleMessage, fullPath } return { consoleMessage, fullPath }
} catch (error) { } catch (error) {
return { error, fullPath } return { error, fullPath }

View File

@@ -62,7 +62,9 @@ const Routes = () => {
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" /> <Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" /> <Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" /> <Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
<Route path="/admin/email" page={AdminEmailPage} name="adminEmail" />
{/* Retired for now but might want to bring it back, delete if older that I danno late 2021 */}
{/* <Route path="/admin/email" page={AdminEmailPage} name="adminEmail" /> */}
</Private> </Private>
</Router> </Router>
) )

View File

@@ -10,7 +10,9 @@ const EditorMenu = () => {
const { state, thunkDispatch } = useIdeContext() const { state, thunkDispatch } = useIdeContext()
const handleStlDownload = makeStlDownloadHandler({ const handleStlDownload = makeStlDownloadHandler({
type: state.objectData?.type, type: state.objectData?.type,
ideType: state.ideType,
geometry: state.objectData?.data, geometry: state.objectData?.data,
quality: state.objectData?.quality,
fileName: PullTitleFromFirstLine(state.code || ''), fileName: PullTitleFromFirstLine(state.code || ''),
thunkDispatch, thunkDispatch,
}) })

View File

@@ -2,7 +2,7 @@ import { flow, identity } from 'lodash/fp'
import { fileSave } from 'browser-fs-access' import { fileSave } from 'browser-fs-access'
import { MeshBasicMaterial, Mesh, Scene } from 'three' import { MeshBasicMaterial, Mesh, Scene } from 'three'
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter' import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
import { requestRender } from 'src/helpers/hooks/useIdeState' import { requestRender, State } from 'src/helpers/hooks/useIdeState'
export const PullTitleFromFirstLine = (code = '') => { export const PullTitleFromFirstLine = (code = '') => {
const firstLine = code.split('\n').filter(identity)[0] || '' const firstLine = code.split('\n').filter(identity)[0] || ''
@@ -16,8 +16,24 @@ export const PullTitleFromFirstLine = (code = '') => {
) )
} }
interface makeStlDownloadHandlerArgs {
geometry: any
fileName: string
type: State['objectData']['type']
ideType: State['ideType']
thunkDispatch: (a: any) => any
quality: State['objectData']['quality']
}
export const makeStlDownloadHandler = export const makeStlDownloadHandler =
({ geometry, fileName, type, thunkDispatch }) => ({
geometry,
fileName,
type,
thunkDispatch,
quality,
ideType,
}: makeStlDownloadHandlerArgs) =>
() => { () => {
const makeStlBlobFromGeo = flow( const makeStlBlobFromGeo = flow(
(geo) => new Mesh(geo, new MeshBasicMaterial()), (geo) => new Mesh(geo, new MeshBasicMaterial()),
@@ -36,22 +52,25 @@ export const makeStlDownloadHandler =
}) })
} }
if (geometry) { if (geometry) {
if (type === 'geometry') { if (
type === 'geometry' &&
(quality === 'high' || ideType === 'openScad')
) {
saveFile(geometry) saveFile(geometry)
} else { } else {
thunkDispatch((dispatch, getState) => { thunkDispatch((dispatch, getState) => {
const state = getState() const state = getState()
if (state.ideType === 'openScad') { const specialCadProcess = ideType === 'openScad' && 'stl'
dispatch({ type: 'setLoading' }) dispatch({ type: 'setLoading' })
requestRender({ requestRender({
state, state,
dispatch, dispatch,
code: state.code, code: state.code,
viewerSize: state.viewerSize, viewerSize: state.viewerSize,
camera: state.camera, camera: state.camera,
specialCadProcess: 'stl', quality: 'high',
}).then((result) => result && saveFile(result.data)) specialCadProcess,
} }).then((result) => result && saveFile(result.data))
}) })
} }
} }

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext' import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { makeCodeStoreKey } from 'src/helpers/hooks/useIdeState' import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
import { requestRender } from 'src/helpers/hooks/useIdeState'
import Editor, { useMonaco } from '@monaco-editor/react' import Editor, { useMonaco } from '@monaco-editor/react'
import { theme } from 'src/../tailwind.config' import { theme } from 'src/../tailwind.config'

View File

@@ -1,5 +1,4 @@
import { makeCodeStoreKey } from 'src/helpers/hooks/useIdeState' import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
import { requestRender } from 'src/helpers/hooks/useIdeState'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext' import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
export const useRender = () => { export const useRender = () => {

View File

@@ -18,10 +18,6 @@ const NavPlusButton: React.FC = () => {
ideType: 'openScad', ideType: 'openScad',
}, },
{ name: 'CadQuery', sub: 'beta', ideType: 'cadQuery' }, { name: 'CadQuery', sub: 'beta', ideType: 'cadQuery' },
{
name: 'CascadeStudio',
sub: 'soon to be deprecated',
},
].map(({ name, sub, ideType }) => ( ].map(({ name, sub, ideType }) => (
<li <li
key={name} key={name}

View File

@@ -12,6 +12,7 @@ import Button from 'src/components/Button'
import PartReactionsCell from '../PartReactionsCell' import PartReactionsCell from '../PartReactionsCell'
import { countEmotes } from 'src/helpers/emote' import { countEmotes } from 'src/helpers/emote'
import { getActiveClasses } from 'get-active-classes' import { getActiveClasses } from 'get-active-classes'
import OutBound from 'src/components/OutBound/OutBound'
const PartProfile = ({ const PartProfile = ({
userPart, userPart,
@@ -178,6 +179,19 @@ const PartProfile = ({
partTitle={input?.title} partTitle={input?.title}
isInvalid={isInvalid} isInvalid={isInvalid}
/> />
<div>
<h3 className="text-center p-2 bg-pink-200 rounded-md mt-4 shadow-md">
Warning, this part was made with CascadeStudio which is being
deprecated on CadHub.{' '}
<OutBound
className="text-gray-600 underline"
to="https://github.com/Irev-Dev/cadhub/discussions/261"
>
Click here
</OutBound>{' '}
for more information
</h3>
</div>
{!isEditable && part?.id && ( {!isEditable && part?.id && (
<ImageUploader <ImageUploader
className="rounded-lg shadow-md border-2 border-gray-200 border-solid mt-8" className="rounded-lg shadow-md border-2 border-gray-200 border-solid mt-8"

View File

@@ -4,12 +4,16 @@ import {
createHealthyResponse, createHealthyResponse,
createUnhealthyResponse, createUnhealthyResponse,
timeoutErrorMessage, timeoutErrorMessage,
RenderArgs,
} from './common' } from './common'
export const render = async ({ code }) => { export const render = async ({
code,
settings: { quality = 'low' },
}: RenderArgs) => {
const body = JSON.stringify({ const body = JSON.stringify({
settings: { settings: {
deflection: 0.2, deflection: quality === 'low' ? 0.35 : 0.11,
}, },
file: code, file: code,
}) })

View File

@@ -1,14 +1,24 @@
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { State } from 'src/helpers/hooks/useIdeState'
export const lambdaBaseURL = export const lambdaBaseURL =
// process.env.CAD_LAMBDA_BASE_URL || process.env.CAD_LAMBDA_BASE_URL ||
'https://oxt2p7ddgj.execute-api.us-east-1.amazonaws.com/prod' 'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2'
export const stlToGeometry = (url) => export const stlToGeometry = (url) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
new STLLoader().load(url, resolve, null, reject) new STLLoader().load(url, resolve, null, reject)
}) })
export interface RenderArgs {
code: State['code']
settings: {
camera: State['camera']
viewerSize: State['viewerSize']
quality: State['objectData']['quality']
}
}
export function createHealthyResponse({ date, data, consoleMessage, type }) { export function createHealthyResponse({ date, data, consoleMessage, type }) {
return { return {
status: 'healthy', status: 'healthy',

View File

@@ -4,9 +4,10 @@ import {
createHealthyResponse, createHealthyResponse,
createUnhealthyResponse, createUnhealthyResponse,
timeoutErrorMessage, timeoutErrorMessage,
RenderArgs,
} from './common' } from './common'
export const render = async ({ code, settings }) => { export const render = async ({ code, settings }: RenderArgs) => {
const pixelRatio = window.devicePixelRatio || 1 const pixelRatio = window.devicePixelRatio || 1
const size = { const size = {
x: Math.round(settings.viewerSize?.width * pixelRatio), x: Math.round(settings.viewerSize?.width * pixelRatio),
@@ -67,7 +68,7 @@ export const render = async ({ code, settings }) => {
} }
} }
export const stl = async ({ code, settings }) => { export const stl = async ({ code, settings }: RenderArgs) => {
const body = JSON.stringify({ const body = JSON.stringify({
settings: {}, settings: {},
file: code, file: code,

View File

@@ -43,36 +43,64 @@ show_object(result)
const codeStorageKey = 'Last-editor-code' const codeStorageKey = 'Last-editor-code'
export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}` export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}`
let mutableState = null let mutableState: State = null
export const useIdeState = () => { interface XYZ {
const code = '' x: number
const initialLayout = { y: number
direction: 'row', z: number
first: 'Editor', }
second: {
direction: 'column', export interface State {
first: 'Viewer', ideType: 'INIT' | 'openScad' | 'cadQuery'
second: 'Console', consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
splitPercentage: 70, code: string
}, objectData: {
type: 'INIT' | 'stl' | 'png' | 'geometry'
data: any
quality: 'low' | 'high'
} }
const initialState = { layout: any
ideType: 'INIT', camera: {
consoleMessages: [ dist?: number
{ type: 'message', message: 'Initialising', time: new Date() }, position?: XYZ
], rotation?: XYZ
code,
objectData: {
type: 'INIT',
data: null,
},
layout: initialLayout,
camera: {},
viewerSize: { width: 0, height: 0 },
isLoading: false,
} }
const reducer = (state, { type, payload }) => { viewerSize: { width: number; height: number }
isLoading: boolean
}
const code = ''
const initialLayout = {
direction: 'row',
first: 'Editor',
second: {
direction: 'column',
first: 'Viewer',
second: 'Console',
splitPercentage: 70,
},
}
export const initialState: State = {
ideType: 'INIT',
consoleMessages: [
{ type: 'message', message: 'Initialising', time: new Date() },
],
code,
objectData: {
type: 'INIT',
data: null,
quality: 'low',
},
layout: initialLayout,
camera: {},
viewerSize: { width: 0, height: 0 },
isLoading: false,
}
export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
const reducer = (state: State, { type, payload }): State => {
switch (type) { switch (type) {
case 'initIde': case 'initIde':
return { return {
@@ -89,6 +117,7 @@ export const useIdeState = () => {
return { return {
...state, ...state,
objectData: { objectData: {
...state.objectData,
type: payload.objectData?.type, type: payload.objectData?.type,
data: payload.objectData?.data, data: payload.objectData?.data,
}, },
@@ -142,8 +171,19 @@ export const useIdeState = () => {
const [state, dispatch] = useReducer(reducer, initialState) const [state, dispatch] = useReducer(reducer, initialState)
mutableState = state mutableState = state
const getState = () => mutableState const getState = (): State => mutableState
return [state, withThunk(dispatch, getState)] const thunkDispatch = withThunk(dispatch, getState)
return [state, thunkDispatch]
}
interface RequestRenderArgs {
state: State
dispatch: any
code: State['code']
camera: State['camera']
viewerSize: State['viewerSize']
quality: State['objectData']['quality']
specialCadProcess?: string
} }
export const requestRender = ({ export const requestRender = ({
@@ -152,8 +192,9 @@ export const requestRender = ({
code, code,
camera, camera,
viewerSize, viewerSize,
quality,
specialCadProcess = null, specialCadProcess = null,
}) => { }: RequestRenderArgs) => {
if ( if (
state.ideType !== 'INIT' && state.ideType !== 'INIT' &&
(!state.isLoading || state.objectData?.type === 'INIT') (!state.isLoading || state.objectData?.type === 'INIT')
@@ -166,6 +207,7 @@ export const requestRender = ({
settings: { settings: {
camera, camera,
viewerSize, viewerSize,
quality,
}, },
}) })
.then(({ objectData, message, status }) => { .then(({ objectData, message, status }) => {

View File

@@ -1,10 +1,18 @@
import { createContext } from 'react' import { createContext } from 'react'
import Seo from 'src/components/Seo/Seo' import Seo from 'src/components/Seo/Seo'
import IdeWrapper from 'src/components/IdeWrapper' import IdeWrapper from 'src/components/IdeWrapper/IdeWrapper'
import { Toaster } from '@redwoodjs/web/toast' import { Toaster } from '@redwoodjs/web/toast'
import { useIdeState } from 'src/helpers/hooks/useIdeState' import { useIdeState, State, initialState } from 'src/helpers/hooks/useIdeState'
export const IdeContext = createContext() interface IdeContextType {
state: State
thunkDispatch: (actionOrThunk: any) => any
}
export const IdeContext = createContext<IdeContextType>({
state: initialState,
thunkDispatch: () => {},
})
const DevIdePage = ({ cadPackage }) => { const DevIdePage = ({ cadPackage }) => {
const [state, thunkDispatch] = useIdeState() const [state, thunkDispatch] = useIdeState()
return ( return (

View File

@@ -40,7 +40,7 @@ No matter which one is your tool of choice, if you're here and you love Code-CAD
- [Community](http://www.openscad.org/community.html) - [Community](http://www.openscad.org/community.html)
- [Docs](http://www.openscad.org/documentation.html) - [Docs](http://www.openscad.org/documentation.html)
- License: GPL-2 - License: GPL-2
- ~~Online editor~~ - [Online editor](https://cadhub.xyz/dev-ide/openScad)
The rest of the packages are in alphabetical order, but OpenSCAD gets a special mention because it's the OG. Many of the projects below were inspired by OpenSCAD and is the most well-known choice. If you're new to code-cad this is the safest choice. The syntax is easy to pick up and there lots of guides around the internet. The rest of the packages are in alphabetical order, but OpenSCAD gets a special mention because it's the OG. Many of the projects below were inspired by OpenSCAD and is the most well-known choice. If you're new to code-cad this is the safest choice. The syntax is easy to pick up and there lots of guides around the internet.
@@ -100,7 +100,7 @@ A community hub for sharing code-cad projects. Currently integrates with [CadQue
- [Community](https://discord.gg/qz3uAdF) - [Community](https://discord.gg/qz3uAdF)
- [Docs](https://cadquery.readthedocs.io/en/latest/intro.html) - [Docs](https://cadquery.readthedocs.io/en/latest/intro.html)
- License: Apache, 2.0 - License: Apache, 2.0
- ~~Online editor~~ - [Online editor](https://cadhub.xyz/dev-ide/cadQuery)
CadQuery is a Python library that wraps and extends [OpenCascade](https://github.com/tpaviot/oce). It has a philosophy of capturing design intent. The API has been designed to be as close as possible to how youd describe the object to a human. An example of this is its ability to "select" parts of the model's geometry to run operations on, such as the following code that selects only the edges running along the Z-axis and fillets them. CadQuery is a Python library that wraps and extends [OpenCascade](https://github.com/tpaviot/oce). It has a philosophy of capturing design intent. The API has been designed to be as close as possible to how youd describe the object to a human. An example of this is its ability to "select" parts of the model's geometry to run operations on, such as the following code that selects only the edges running along the Z-axis and fillets them.