Completed initial CAD package guides, tweaked initial code import

This commit is contained in:
Frank Johnson
2021-09-18 23:16:43 -04:00
parent 2f006d3e3b
commit b27bcd2d35
17 changed files with 297 additions and 139 deletions

View File

@@ -5,5 +5,9 @@ module.exports = (config, { env }) => {
plugin.userOptions.favicon = './src/favicon.svg'
}
})
config.module.rules.push({
test: /\.md$|\.jscad$|\.py$|\.SCAD$/i,
use: 'raw-loader',
});
return config
}

View File

@@ -58,6 +58,7 @@
"postcss": "^8.3.6",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.1.1",
"raw-loader": "^4.0.2",
"tailwindcss": "^2.2.7"
}
}

View File

@@ -1,43 +1,63 @@
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'INIT'
export const ideTypeNameMap: { [key in CadPackageType]: string } = {
openscad: 'OpenSCAD',
cadquery: 'CadQuery',
jscad: 'JSCAD',
interface CadPackageConfig {
label: string
buttonClasses: string
dotClasses: string
}
export const cadPackageConfigs: { [key in CadPackageType]: CadPackageConfig } =
{
openscad: {
label: 'OpenSCAD',
buttonClasses: 'bg-yellow-800',
dotClasses: 'bg-yellow-200',
},
cadquery: {
label: 'CadQuery',
buttonClasses: 'bg-ch-blue-700',
dotClasses: 'bg-blue-800',
},
jscad: {
label: 'JSCAD',
buttonClasses: 'bg-ch-purple-500',
dotClasses: 'bg-yellow-300',
},
INIT: {
label: '',
buttonClasses: '',
dotClasses: '',
},
}
interface CadPackageProps {
cadPackage: CadPackageType
className?: string
dotClass?: string
onClick?: any
}
const CadPackage = ({
cadPackage,
className = '',
dotClass = 'w-5 h-5',
onClick,
}: CadPackageProps) => {
const cadName = ideTypeNameMap[cadPackage] || ''
const isOpenScad = cadPackage === 'openscad'
const isCadQuery = cadPackage === 'cadquery'
const isJsCad = cadPackage === 'jscad'
const cadPackageConfig = cadPackageConfigs[cadPackage]
return (
<div
<button
onClick={onClick}
className={
`grid grid-flow-col-dense items-center gap-2 text-gray-100 ${
isOpenScad && 'bg-yellow-800'
} ${isCadQuery && 'bg-ch-blue-700'} ${
isJsCad && 'bg-ch-purple-500'
} bg-opacity-30 ` + className
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30 hover:bg-opacity-80 ${cadPackageConfig.buttonClasses} ` +
className
}
>
<div
className={`${isOpenScad && 'bg-yellow-200'} ${
isCadQuery && 'bg-blue-800'
} ${isJsCad && 'bg-yellow-300'} ${dotClass} rounded-full`}
className={`${cadPackageConfig.dotClasses} ${dotClass} rounded-full`}
/>
<div>{cadName}</div>
</div>
{cadPackageConfig.label}
</button>
)
}

View File

@@ -0,0 +1,53 @@
import { extractMetaData } from 'src/helpers/markdown'
import Editor from 'rich-markdown-editor'
import { useRef } from 'react'
import KeyValue from 'src/components/KeyValue/KeyValue'
export default function EditorGuide({ content }) {
const [rawMetadata, metadata] = extractMetaData(content)
const processedContent = rawMetadata
? content.replace(rawMetadata[0], '')
: content
const ref = useRef(null)
return (
<div className="markdown-overrides py-6 px-8">
{metadata && (
<>
<h1 className="my-4">{metadata.title}</h1>
<section className="grid grid-cols-3 my-6 gap-y-4">
{Object.entries(metadata)
.filter(([key]) => key !== 'title')
.map(([key, value], i) => (
<KeyValue keyName={key.replace(/"/g, '')} key={key + '-' + i}>
<LinkOrParagraph>{value}</LinkOrParagraph>
</KeyValue>
))}
</section>
</>
)}
<Editor
ref={ref}
readOnly={true}
defaultValue={processedContent}
value={processedContent}
onChange={() => {}}
/>
</div>
)
}
function LinkOrParagraph({ children }) {
const markdownUrlExpression =
/\[(.*)\]\((https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})\)/i
const matches = children.match(markdownUrlExpression)
return matches === null ? (
<p>{children}</p>
) : (
<a href={matches[2]} rel="noopener noreferrer" target="_blank">
{matches[1]}
</a>
)
}

View File

@@ -48,7 +48,24 @@ const EditorMenu = () => {
<Svg name="gear" className="w-6 p-px" />
</button>
</div>
<CadPackage cadPackage={state.ideType} className="px-3" />
<CadPackage
cadPackage={state.ideType}
className="px-3"
onClick={() => {
thunkDispatch({
type: 'addEditorModel',
payload: {
type: 'guide',
label: 'Guide',
content: state.ideGuide,
},
})
thunkDispatch({
type: 'switchEditorModel',
payload: state.models.length,
})
}}
/>
</div>
<AllShortcutsModal />
</>

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { ideTypeNameMap } from 'src/components/CadPackage/CadPackage'
import { cadPackageConfigs } from 'src/components/CadPackage/CadPackage'
import OutBound from 'src/components/OutBound/OutBound'
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
import { copyTextToClipboard } from 'src/helpers/clipboard'
@@ -16,7 +16,7 @@ const ExternalScript = () => {
'INIT' | 'SUCCESS' | 'ERROR' | 'LOADING'
>('INIT')
const cadName = ideTypeNameMap[state.ideType]
const cadName = cadPackageConfigs[state.ideType].label
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = async ({
clipboardData,

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
import Editor, { useMonaco } from '@monaco-editor/react'
import { theme } from 'src/../config/tailwind.config'
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
import EditorGuide from 'src/components/EditorGuide/EditorGuide'
const colors = theme.extend.colors
@@ -17,6 +18,7 @@ const IdeEditor = ({ Loading }) => {
cadquery: 'python',
openscad: 'cpp',
jscad: 'javascript',
INIT: '',
}
const monaco = useMonaco()
useEffect(() => {
@@ -73,34 +75,50 @@ const IdeEditor = ({ Loading }) => {
className="h-full"
onKeyDown={handleSaveHotkey}
>
{ (state.models.length > 1) && (
<fieldset
className='bg-ch-gray-700 text-ch-gray-300 flex m-0 p-0'>
{ state.models.map((model, i) => (
<label key={model.type + '-' + i}
className={'flex items-center gap-2 px-4 py-1 block m-0 select-none relative bg-ch-gray-600 ' + ((state.currentModel === i) && 'bg-ch-gray-800')}>
{ model.label }
{state.models.length > 1 && (
<fieldset className="bg-ch-gray-700 text-ch-gray-300 flex m-0 p-0">
{state.models.map((model, i) => (
<label
key={model.type + '-' + i}
className={
'flex items-center gap-2 px-4 py-1 block m-0 select-none relative bg-ch-gray-600 ' +
(state.currentModel === i && 'bg-ch-gray-800')
}
>
{model.label}
<input
type='radio'
name='models'
className='sr-only absolute inset-0'
type="radio"
name="models"
className="sr-only absolute inset-0"
value={i}
checked={state.currentModel === i}
onChange={() => thunkDispatch({ type: 'switchEditorModel', payload: i })} />
{ (model.type !== 'code') &&
<button onClick={() => thunkDispatch({ type: 'removeEditorModel', payload: i })}
className='block p-1 m-.5 hover:bg-ch-gray-550' >
<svg viewBox='0 0 5 5' className='w-4 text-ch-gray-300'>
<path stroke='currentColor' d='M 1 1 l 3 3 M 1 4 l 3 -3' strokeLinecap='round' strokeWidth='.5' />
</svg>
</button>
}
onChange={() =>
thunkDispatch({ type: 'switchEditorModel', payload: i })
}
/>
{model.type !== 'code' && (
<button
onClick={() =>
thunkDispatch({ type: 'removeEditorModel', payload: i })
}
className="block p-1 m-.5 hover:bg-ch-gray-550"
>
<svg viewBox="0 0 5 5" className="w-4 text-ch-gray-300">
<path
stroke="currentColor"
d="M 1 1 l 3 3 M 1 4 l 3 -3"
strokeLinecap="round"
strokeWidth=".5"
/>
</svg>
</button>
)}
</label>
)) }
))}
</fieldset>
)}
{ (state.models[state.currentModel].type === 'code')
? <Editor
{state.models[state.currentModel].type === 'code' ? (
<Editor
defaultValue={state.code}
value={state.code}
theme={theme}
@@ -110,8 +128,11 @@ const IdeEditor = ({ Loading }) => {
language={ideTypeToLanguageMap[state.ideType] || 'cpp'}
onChange={handleCodeChange}
/>
: <pre className="bg-ch-gray-800 text-ch-gray-300 p-6 h-full">{ state.models[state.currentModel].content }</pre>
}
) : (
<div className="bg-ch-gray-800 h-full">
<EditorGuide content={state.models[state.currentModel].content} />
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,17 @@
# demo shaft coupler
# ^ first comment is used for download title (i.e. "demo-shaft-coupler.stl")
# CadQuery docs: https://cadquery.readthedocs.io/
import cadquery as cq
from cadquery import exporters
diam = 5.0
result = (cq.Workplane().circle(diam).extrude(20.0)
.faces(">Z").workplane(invert=True).circle(1.05).cutBlind(8.0)
.faces("<Z").workplane(invert=True).circle(0.8).cutBlind(12.0)
.edges("%CIRCLE").chamfer(0.15))
show_object(result)

View File

@@ -1,9 +1,10 @@
---
"Written with": Python
"Kernal type": BREP
title: CadQuery
Written with: Python
Kernal type: BREP
Maintained by: [The CadQuery Group](https://github.com/CadQuery)
Documentation: [cadquery.readthedocs.io](https://cadquery.readthedocs.io)
---
# CadQuery
CadQuery is an intuitive, easy-to-use Python library for building parametric 3D CAD models. It has several goals:
- Build models with scripts that are as close as possible to how youd describe the object to a human, using a standard, already established programming language

View File

@@ -0,0 +1,33 @@
const jscad = require('@jscad/modeling')
// https://openjscad.xyz/docs/module-modeling_primitives.html
const { cuboid, cylinder } = jscad.primitives
const { rotate, translate } = jscad.transforms
const { degToRad } = jscad.utils // because jscad uses radians for rotations
// https://openjscad.xyz/docs/module-modeling_booleans.html
const { subtract } = jscad.booleans
function main({//@jscad-params
// Box example
width=40, // Width
length=20, // Length
height=10, // Height
hole=3,// Hole for cables diameter (0=no hole)
wall=1, // wall {min:0.5, step:0.5}
flip=0, // print orientation {type: 'choice', values: [0, 90, 180]}
}){
let wallOffset = wall * 2
let model = subtract(
cuboid({size:[width, length, height]}),
translate([0,0,wall], cuboid({size:[width-wallOffset, length-wallOffset, height+wall]})),
)
if(hole){
model = subtract( model,
translate([width/2-wall/2], rotate([0, degToRad(90), 0 ], cylinder({radius:hole/2, height:wall})))
)
}
return rotate([degToRad(flip), 0, degToRad(90)], model)
}
module.exports = {main}

View File

@@ -1,7 +1,8 @@
---
"Written with": JavaScript
"Kernal type": BREP
title: JSCAD
Written with: JavaScript
Kernal type: BREP
Maintained by: [Z3 Development + 39 contributors](https://www.github.com/jscad)
Documentation: [openjscad.xyz/docs](https://openjscad.xyz/docs/)
---
# JSCAD
JSCAD is an open source set of modular, browser and command line tools for creating parametric 2D and 3D designs with Javascript code. It provides a quick, precise and reproducible method for generating 3D models, and is especially useful for 3D printing applications.

View File

@@ -0,0 +1,12 @@
// involute donut
// ^ first comment is used for download title (i.e "involute-donut.stl")
// Follow the OpenSCAD tutorial: https://learn.cadhub.xyz/docs/
radius=3;
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
color(c="hotpink")rotate_extrude()translate([20,0])offset(radius)offset(-radius)difference(){
circle(d=34);
translate([-200,-500])square([500,500]);
}

View File

@@ -1,7 +1,8 @@
---
"Written with": C+-like
"Kernal type": BREP
title: OpenSCAD
Written with: Custom language
Kernal type: BREP
Maintained by: [Marius Kintel + 15 members](https://github.com/openscad)
Documentation: [openscad.org](https://openscad.org/)
---
# OpenSCAD
OpenSCAD is a solid 3D modeler that enables the creation of parametric models using its scripting language. Models are created by utilizing a technique called constructive solid geometry. According to this technique, simple objects can be transformed and combined in order to create almost any complex model.

View File

@@ -6,6 +6,14 @@ import type {
ArtifactTypes,
} from 'src/helpers/cadPackages/common'
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
import openScadGuide from 'src/helpers/cadPackages/openScad/userGuide.md'
import openScadInitialCode from 'src/helpers/cadPackages/openScad/initialCode.SCAD'
import cadQueryGuide from 'src/helpers/cadPackages/cadQuery/userGuide.md'
import cadQueryInitialCode from 'src/helpers/cadPackages/cadQuery/initialCode.py'
import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md'
import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad'
console.log('jscad', { jsCadInitialCode })
function withThunk(dispatch, getState) {
return (actionOrThunk) =>
@@ -15,74 +23,18 @@ function withThunk(dispatch, getState) {
}
import { CadPackageType } from 'src/components/CadPackage/CadPackage'
const initCodeMap: { [key in CadPackageType]: string } = {
openscad: `// involute donut
// ^ first comment is used for download title (i.e "involute-donut.stl")
// Follow the OpenSCAD tutorial: https://learn.cadhub.xyz/docs/
radius=3;
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
color(c="hotpink")rotate_extrude()translate([20,0])offset(radius)offset(-radius)difference(){
circle(d=34);
translate([-200,-500])square([500,500]);
}`,
cadquery: `# demo shaft coupler
# ^ first comment is used for download title (i.e. "demo-shaft-coupler.stl")
# CadQuery docs: https://cadquery.readthedocs.io/
import cadquery as cq
from cadquery import exporters
diam = 5.0
result = (cq.Workplane().circle(diam).extrude(20.0)
.faces(">Z").workplane(invert=True).circle(1.05).cutBlind(8.0)
.faces("<Z").workplane(invert=True).circle(0.8).cutBlind(12.0)
.edges("%CIRCLE").chamfer(0.15))
show_object(result)
`,
jscad: `
const jscad = require('@jscad/modeling')
// https://openjscad.xyz/docs/module-modeling_primitives.html
const { cuboid, cylinder } = jscad.primitives
const { rotate, translate } = jscad.transforms
const { degToRad } = jscad.utils // because jscad uses radians for rotations
// https://openjscad.xyz/docs/module-modeling_booleans.html
const { subtract } = jscad.booleans
function main({//@jscad-params
// Box example
width=40, // Width
length=20, // Length
height=10, // Height
hole=3,// Hole for cables diameter (0=no hole)
wall=1, // wall {min:0.5, step:0.5}
flip=0, // print orientation {type: 'choice', values: [0, 90, 180]}
}){
let wallOffset = wall * 2
let model = subtract(
cuboid({size:[width, length, height]}),
translate([0,0,wall], cuboid({size:[width-wallOffset, length-wallOffset, height+wall]})),
)
if(hole){
model = subtract( model,
translate([width/2-wall/2], rotate([0, degToRad(90), 0 ], cylinder({radius:hole/2, height:wall})))
)
}
return rotate([degToRad(flip), 0, degToRad(90)], model)
const initGuideMap: { [key in CadPackageType]: string } = {
openscad: openScadGuide,
cadquery: cadQueryGuide,
jscad: jsCadGuide,
INIT: '',
}
module.exports = {main}
`,
const initCodeMap: { [key in CadPackageType]: string } = {
openscad: openScadInitialCode,
cadquery: cadQueryInitialCode,
jscad: jsCadInitialCode,
INIT: '',
}
const codeStorageKey = 'Last-editor-code'
@@ -103,6 +55,7 @@ interface EditorModel {
export interface State {
ideType: 'INIT' | CadPackageType
ideGuide?: string
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
code: string
models: EditorModel[]
@@ -144,10 +97,7 @@ export const initialState: State = {
{ type: 'message', message: 'Initialising', time: new Date() },
],
code,
models: [
{ type: 'code', label: 'Code', },
{ type: 'guide', label: 'Test', content: '# Testing!' },
],
models: [{ type: 'code', label: 'Code' }],
currentModel: 0,
objectData: {
type: 'INIT',
@@ -174,6 +124,7 @@ const reducer = (state: State, { type, payload }): State => {
initCodeMap[payload.cadPackage] ||
'',
ideType: payload.cadPackage,
ideGuide: initGuideMap[payload.cadPackage],
}
case 'updateCode':
return { ...state, code: payload }
@@ -290,10 +241,7 @@ const reducer = (state: State, { type, payload }): State => {
case 'addEditorModel':
return {
...state,
models: [
...state.models,
payload,
]
models: [...state.models, payload],
}
case 'removeEditorModel':
return {
@@ -302,10 +250,10 @@ const reducer = (state: State, { type, payload }): State => {
...state.models.slice(0, payload),
...state.models.slice(payload + 1),
],
currentModel: (payload === 0) ? 0 : payload - 1,
currentModel: payload === 0 ? 0 : payload - 1,
}
case 'updateEditorModel': {
let newModels = [...state.models]
const newModels = [...state.models]
newModels[state.currentModel].content = payload
return {
...state,
@@ -313,9 +261,9 @@ const reducer = (state: State, { type, payload }): State => {
}
}
case 'reorderEditorModels': {
let newModels = [
const newModels = [
...state.models.slice(0, state.currentModel),
...state.models.slice(state.currentModel + 1)
...state.models.slice(state.currentModel + 1),
].splice(payload, 0, state.models[state.currentModel])
return {
...state,

View File

@@ -0,0 +1,24 @@
// Extracts YAML frontmatter from Markdown files
// Gotten from this helpful comment on a react-markdown GitHub Issue: https://github.com/remarkjs/react-markdown/issues/164#issuecomment-890497653
export function extractMetaData(text: string): Array<any> {
const metaData = {} as any
const metaRegExp = RegExp(/^---[\r\n](((?!---).|[\r\n])*)[\r\n]---$/m)
// get metadata
const rawMetaData = metaRegExp.exec(text)
let keyValues
if (rawMetaData!) {
// rawMeta[1] are the stuff between "---"
keyValues = rawMetaData[1].split('\n')
// which returns a list of key values: ["key1: value", "key2: value"]
keyValues.forEach((keyValue) => {
// split each keyValue to keys and values
const [, key, value] = keyValue.split(/(.+): (.+)/)
metaData[key] = value.trim()
})
}
return [rawMetaData, metaData]
}

View File

@@ -48,19 +48,24 @@
.markdown-overrides {
@apply bg-transparent;
}
.markdown-overrides,
.markdown-overrides div {
@apply text-ch-gray-300 bg-transparent;
}
.markdown-overrides a,
.markdown-overrides div a {
@apply text-ch-pink-500 underline bg-transparent;
}
.markdown-overrides h3,
.markdown-overrides div h3 {
@apply text-xl;
}
.markdown-overrides h2,
.markdown-overrides div h2 {
@apply text-2xl;
}
.markdown-overrides h1,
.markdown-overrides div h1 {
@apply text-3xl;
}

View File

@@ -15425,7 +15425,7 @@ raw-body@2.4.0:
raw-loader@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
dependencies:
loader-utils "^2.0.0"