Merge pull request #519 from Irev-Dev/editor-tabs
Initial editor tabs implementation with CAD package guides
This commit was merged in pull request #519.
This commit is contained in:
@@ -5,5 +5,9 @@ module.exports = (config, { env }) => {
|
|||||||
plugin.userOptions.favicon = './src/favicon.svg'
|
plugin.userOptions.favicon = './src/favicon.svg'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.(md|jscad\.js|py|scad)$/i,
|
||||||
|
use: 'raw-loader',
|
||||||
|
});
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"postcss": "^8.3.6",
|
"postcss": "^8.3.6",
|
||||||
"postcss-import": "^14.0.2",
|
"postcss-import": "^14.0.2",
|
||||||
"postcss-loader": "^6.1.1",
|
"postcss-loader": "^6.1.1",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"tailwindcss": "^2.2.7"
|
"tailwindcss": "^2.2.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,75 @@
|
|||||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
|
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'INIT'
|
||||||
|
|
||||||
export const ideTypeNameMap: { [key in CadPackageType]: string } = {
|
interface CadPackageConfig {
|
||||||
openscad: 'OpenSCAD',
|
label: string
|
||||||
cadquery: 'CadQuery',
|
buttonClasses: string
|
||||||
jscad: 'JSCAD',
|
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 {
|
interface CadPackageProps {
|
||||||
cadPackage: CadPackageType
|
cadPackage: CadPackageType
|
||||||
className?: string
|
className?: string
|
||||||
dotClass?: string
|
dotClass?: string
|
||||||
|
onClick?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const CadPackage = ({
|
const CadPackage = ({
|
||||||
cadPackage,
|
cadPackage,
|
||||||
className = '',
|
className = '',
|
||||||
dotClass = 'w-5 h-5',
|
dotClass = 'w-5 h-5',
|
||||||
|
onClick,
|
||||||
}: CadPackageProps) => {
|
}: CadPackageProps) => {
|
||||||
const cadName = ideTypeNameMap[cadPackage] || ''
|
const cadPackageConfig = cadPackageConfigs[cadPackage]
|
||||||
const isOpenScad = cadPackage === 'openscad'
|
|
||||||
const isCadQuery = cadPackage === 'cadquery'
|
|
||||||
const isJsCad = cadPackage === 'jscad'
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ButtonOrDiv
|
||||||
|
onClick={onClick}
|
||||||
className={
|
className={
|
||||||
`grid grid-flow-col-dense items-center gap-2 text-gray-100 ${
|
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30
|
||||||
isOpenScad && 'bg-yellow-800'
|
${cadPackageConfig?.buttonClasses} ` + className
|
||||||
} ${isCadQuery && 'bg-ch-blue-700'} ${
|
|
||||||
isJsCad && 'bg-ch-purple-500'
|
|
||||||
} bg-opacity-30 ` + className
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`${isOpenScad && 'bg-yellow-200'} ${
|
className={`${cadPackageConfig?.dotClasses} ${dotClass} rounded-full`}
|
||||||
isCadQuery && 'bg-blue-800'
|
|
||||||
} ${isJsCad && 'bg-yellow-300'} ${dotClass} rounded-full`}
|
|
||||||
/>
|
/>
|
||||||
<div>{cadName}</div>
|
{cadPackageConfig?.label}
|
||||||
</div>
|
</ButtonOrDiv>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a proper button if an onClick handler is passed in, or a div
|
||||||
|
// if the element is meant to be a simple badge
|
||||||
|
function ButtonOrDiv({ onClick, className, children }) {
|
||||||
|
return onClick ? (
|
||||||
|
<button className={className + ' hover:bg-opacity-80'} onClick={onClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className={className}>{children}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
53
app/web/src/components/EditorGuide/EditorGuide.tsx
Normal file
53
app/web/src/components/EditorGuide/EditorGuide.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { useMarkdownMetaData } from 'src/helpers/hooks/useMarkdownMetaData'
|
||||||
|
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] = useMarkdownMetaData(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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -48,7 +48,24 @@ const EditorMenu = () => {
|
|||||||
<Svg name="gear" className="w-6 p-px" />
|
<Svg name="gear" className="w-6 p-px" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<AllShortcutsModal />
|
<AllShortcutsModal />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
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 OutBound from 'src/components/OutBound/OutBound'
|
||||||
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
|
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
|
||||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||||
@@ -16,7 +16,7 @@ const ExternalScript = () => {
|
|||||||
'INIT' | 'SUCCESS' | 'ERROR' | 'LOADING'
|
'INIT' | 'SUCCESS' | 'ERROR' | 'LOADING'
|
||||||
>('INIT')
|
>('INIT')
|
||||||
|
|
||||||
const cadName = ideTypeNameMap[state.ideType]
|
const cadName = cadPackageConfigs[state.ideType].label
|
||||||
|
|
||||||
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = async ({
|
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = async ({
|
||||||
clipboardData,
|
clipboardData,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||||
import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
|
import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
|
||||||
import Editor, { useMonaco } from '@monaco-editor/react'
|
import Editor, { useMonaco } from '@monaco-editor/react'
|
||||||
import { theme } from 'src/../config/tailwind.config'
|
import { theme } from 'src/../config/tailwind.config'
|
||||||
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
|
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
|
||||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||||
|
import EditorGuide from 'src/components/EditorGuide/EditorGuide'
|
||||||
|
|
||||||
const colors = theme.extend.colors
|
const colors = theme.extend.colors
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ const IdeEditor = ({ Loading }) => {
|
|||||||
cadquery: 'python',
|
cadquery: 'python',
|
||||||
openscad: 'cpp',
|
openscad: 'cpp',
|
||||||
jscad: 'javascript',
|
jscad: 'javascript',
|
||||||
|
INIT: '',
|
||||||
}
|
}
|
||||||
const monaco = useMonaco()
|
const monaco = useMonaco()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -73,16 +75,64 @@ const IdeEditor = ({ Loading }) => {
|
|||||||
className="h-full"
|
className="h-full"
|
||||||
onKeyDown={handleSaveHotkey}
|
onKeyDown={handleSaveHotkey}
|
||||||
>
|
>
|
||||||
<Editor
|
{state.models.length > 1 && (
|
||||||
defaultValue={state.code}
|
<fieldset className="bg-ch-gray-700 text-ch-gray-300 flex m-0 p-0">
|
||||||
value={state.code}
|
{state.models.map((model, i) => (
|
||||||
theme={theme}
|
<label
|
||||||
loading={Loading}
|
key={model.type + '-' + i}
|
||||||
// TODO #247 cpp seems better than js for the time being
|
className={
|
||||||
defaultLanguage={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
'flex items-center gap-2 px-4 py-1 block m-0 select-none relative bg-ch-gray-600 ' +
|
||||||
language={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
(state.currentModel === i && 'bg-ch-gray-800')
|
||||||
onChange={handleCodeChange}
|
}
|
||||||
/>
|
>
|
||||||
|
{model.label}
|
||||||
|
<input
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</fieldset>
|
||||||
|
)}
|
||||||
|
{state.models[state.currentModel].type === 'code' ? (
|
||||||
|
<Editor
|
||||||
|
defaultValue={state.code}
|
||||||
|
value={state.code}
|
||||||
|
theme={theme}
|
||||||
|
loading={Loading}
|
||||||
|
// TODO #247 cpp seems better than js for the time being
|
||||||
|
defaultLanguage={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
||||||
|
language={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
||||||
|
onChange={handleCodeChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="bg-ch-gray-800 h-full">
|
||||||
|
<EditorGuide content={state.models[state.currentModel].content} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const QUERY = gql`
|
|||||||
id
|
id
|
||||||
title
|
title
|
||||||
mainImage
|
mainImage
|
||||||
|
cadPackage
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
user {
|
user {
|
||||||
|
|||||||
7
app/web/src/globals.d.ts
vendored
Normal file
7
app/web/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// While the raw-loader Webpack plugin actually makes these imports work, this
|
||||||
|
// eliminates noisy TypeScript errors by registering these file endings as types.
|
||||||
|
// Learned this method of registering modules from https://stackoverflow.com/a/57444766
|
||||||
|
declare module '*.md'
|
||||||
|
declare module '*.scad'
|
||||||
|
declare module '*.py'
|
||||||
|
declare module '*.jscad.js'
|
||||||
17
app/web/src/helpers/cadPackages/cadQuery/initialCode.py
Normal file
17
app/web/src/helpers/cadPackages/cadQuery/initialCode.py
Normal 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)
|
||||||
13
app/web/src/helpers/cadPackages/cadQuery/userGuide.md
Normal file
13
app/web/src/helpers/cadPackages/cadQuery/userGuide.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
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 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 you’d describe the object to a human, using a standard, already established programming language
|
||||||
|
- Create parametric models that can be very easily customized by end users
|
||||||
|
- Output high quality CAD formats like STEP and AMF in addition to traditional STL
|
||||||
|
- Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
|
||||||
@@ -2,11 +2,33 @@ import { DefaultKernelExport } from './common'
|
|||||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||||
|
|
||||||
import openscad from './openScad/openScadController'
|
import openscad from './openScad/openScadController'
|
||||||
|
import openScadGuide from 'src/helpers/cadPackages/openScad/userGuide.md'
|
||||||
|
import openScadInitialCode from 'src/helpers/cadPackages/openScad/initialCode.scad'
|
||||||
|
|
||||||
import cadquery from './cadQueryController'
|
import cadquery from './cadQueryController'
|
||||||
|
import cadQueryGuide from 'src/helpers/cadPackages/cadQuery/userGuide.md'
|
||||||
|
import cadQueryInitialCode from 'src/helpers/cadPackages/cadQuery/initialCode.py'
|
||||||
|
|
||||||
import jscad from './jsCad/jsCadController'
|
import jscad from './jsCad/jsCadController'
|
||||||
|
import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md'
|
||||||
|
import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad.js'
|
||||||
|
|
||||||
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
||||||
openscad,
|
openscad,
|
||||||
cadquery,
|
cadquery,
|
||||||
jscad,
|
jscad,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const initGuideMap: { [key in CadPackageType]: string } = {
|
||||||
|
openscad: openScadGuide,
|
||||||
|
cadquery: cadQueryGuide,
|
||||||
|
jscad: jsCadGuide,
|
||||||
|
INIT: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initCodeMap: { [key in CadPackageType]: string } = {
|
||||||
|
openscad: openScadInitialCode,
|
||||||
|
cadquery: cadQueryInitialCode,
|
||||||
|
jscad: jsCadInitialCode,
|
||||||
|
INIT: '',
|
||||||
|
}
|
||||||
|
|||||||
43
app/web/src/helpers/cadPackages/jsCad/initialCode.jscad.js
Normal file
43
app/web/src/helpers/cadPackages/jsCad/initialCode.jscad.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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 }
|
||||||
8
app/web/src/helpers/cadPackages/jsCad/userGuide.md
Normal file
8
app/web/src/helpers/cadPackages/jsCad/userGuide.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: JSCAD
|
||||||
|
Written with: JavaScript
|
||||||
|
Kernal type: Mesh CSG
|
||||||
|
Maintained by: [Z3 Development + 39 contributors](https://www.github.com/jscad)
|
||||||
|
Documentation: [openjscad.xyz/docs](https://openjscad.xyz/docs/)
|
||||||
|
---
|
||||||
|
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.
|
||||||
12
app/web/src/helpers/cadPackages/openScad/initialCode.SCAD
Normal file
12
app/web/src/helpers/cadPackages/openScad/initialCode.SCAD
Normal 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]);
|
||||||
|
}
|
||||||
8
app/web/src/helpers/cadPackages/openScad/userGuide.md
Normal file
8
app/web/src/helpers/cadPackages/openScad/userGuide.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: OpenSCAD
|
||||||
|
Written with: [Domain-Specific Language](https://martinfowler.com/dsl.html)
|
||||||
|
Kernal type: Mesh CSG
|
||||||
|
Maintained by: [Marius Kintel and contributors](https://github.com/openscad/openscad/graphs/contributors)
|
||||||
|
Documentation: [openscad.org](https://openscad.org/)
|
||||||
|
---
|
||||||
|
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.
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useReducer } from 'react'
|
import { useReducer } from 'react'
|
||||||
import { cadPackages } from 'src/helpers/cadPackages'
|
import { cadPackages, initCodeMap, initGuideMap } from 'src/helpers/cadPackages'
|
||||||
import type { RootState } from '@react-three/fiber'
|
import type { RootState } from '@react-three/fiber'
|
||||||
import type {
|
import type {
|
||||||
RawCustomizerParams,
|
RawCustomizerParams,
|
||||||
ArtifactTypes,
|
ArtifactTypes,
|
||||||
} from 'src/helpers/cadPackages/common'
|
} from 'src/helpers/cadPackages/common'
|
||||||
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
||||||
|
import { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||||
|
|
||||||
function withThunk(dispatch, getState) {
|
function withThunk(dispatch, getState) {
|
||||||
return (actionOrThunk) =>
|
return (actionOrThunk) =>
|
||||||
@@ -13,77 +14,6 @@ function withThunk(dispatch, getState) {
|
|||||||
? actionOrThunk(dispatch, getState)
|
? actionOrThunk(dispatch, getState)
|
||||||
: dispatch(actionOrThunk)
|
: dispatch(actionOrThunk)
|
||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {main}
|
|
||||||
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeStorageKey = 'Last-editor-code'
|
const codeStorageKey = 'Last-editor-code'
|
||||||
export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}`
|
export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}`
|
||||||
@@ -95,10 +25,19 @@ interface XYZ {
|
|||||||
z: number
|
z: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditorModel {
|
||||||
|
type: 'code' | 'guide'
|
||||||
|
label: string
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
ideType: 'INIT' | CadPackageType
|
ideType: 'INIT' | CadPackageType
|
||||||
|
ideGuide?: string
|
||||||
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
|
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
|
||||||
code: string
|
code: string
|
||||||
|
models: EditorModel[]
|
||||||
|
currentModel: number
|
||||||
objectData: {
|
objectData: {
|
||||||
type: 'INIT' | ArtifactTypes
|
type: 'INIT' | ArtifactTypes
|
||||||
data: any
|
data: any
|
||||||
@@ -136,6 +75,8 @@ export const initialState: State = {
|
|||||||
{ type: 'message', message: 'Initialising', time: new Date() },
|
{ type: 'message', message: 'Initialising', time: new Date() },
|
||||||
],
|
],
|
||||||
code,
|
code,
|
||||||
|
models: [{ type: 'code', label: 'Code' }],
|
||||||
|
currentModel: 0,
|
||||||
objectData: {
|
objectData: {
|
||||||
type: 'INIT',
|
type: 'INIT',
|
||||||
data: null,
|
data: null,
|
||||||
@@ -161,6 +102,7 @@ const reducer = (state: State, { type, payload }): State => {
|
|||||||
initCodeMap[payload.cadPackage] ||
|
initCodeMap[payload.cadPackage] ||
|
||||||
'',
|
'',
|
||||||
ideType: payload.cadPackage,
|
ideType: payload.cadPackage,
|
||||||
|
ideGuide: initGuideMap[payload.cadPackage],
|
||||||
}
|
}
|
||||||
case 'updateCode':
|
case 'updateCode':
|
||||||
return { ...state, code: payload }
|
return { ...state, code: payload }
|
||||||
@@ -269,6 +211,44 @@ const reducer = (state: State, { type, payload }): State => {
|
|||||||
...state,
|
...state,
|
||||||
sideTray: payload,
|
sideTray: payload,
|
||||||
}
|
}
|
||||||
|
case 'switchEditorModel':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentModel: payload,
|
||||||
|
}
|
||||||
|
case 'addEditorModel':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
models: [...state.models, payload],
|
||||||
|
}
|
||||||
|
case 'removeEditorModel':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
models: [
|
||||||
|
...state.models.slice(0, payload),
|
||||||
|
...state.models.slice(payload + 1),
|
||||||
|
],
|
||||||
|
currentModel: payload === 0 ? 0 : payload - 1,
|
||||||
|
}
|
||||||
|
// case 'updateEditorModel': {
|
||||||
|
// const newModels = [...state.models]
|
||||||
|
// newModels[state.currentModel].content = payload
|
||||||
|
// return {
|
||||||
|
// ...state,
|
||||||
|
// models: newModels,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// case 'reorderEditorModels': {
|
||||||
|
// const newModels = [
|
||||||
|
// ...state.models.slice(0, state.currentModel),
|
||||||
|
// ...state.models.slice(state.currentModel + 1),
|
||||||
|
// ].splice(payload, 0, state.models[state.currentModel])
|
||||||
|
// return {
|
||||||
|
// ...state,
|
||||||
|
// models: newModels,
|
||||||
|
// currentModel: payload,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/web/src/helpers/hooks/useMarkdownMetaData.ts
Normal file
27
app/web/src/helpers/hooks/useMarkdownMetaData.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 useMarkdownMetaData(text: string): Array<any> {
|
||||||
|
const metaData = {} as any
|
||||||
|
return React.useMemo(() => {
|
||||||
|
const metaRegExp = RegExp(
|
||||||
|
/^---[\r\n](((?!---).|[\r\n])*)[\r\n]---$/m
|
||||||
|
) as any
|
||||||
|
// get metadata
|
||||||
|
const rawMetaData = metaRegExp.exec(text)
|
||||||
|
|
||||||
|
let keyValues
|
||||||
|
|
||||||
|
if (rawMetaData !== null) {
|
||||||
|
// 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]
|
||||||
|
}, [text])
|
||||||
|
}
|
||||||
@@ -48,19 +48,24 @@
|
|||||||
.markdown-overrides {
|
.markdown-overrides {
|
||||||
@apply bg-transparent;
|
@apply bg-transparent;
|
||||||
}
|
}
|
||||||
|
.markdown-overrides,
|
||||||
.markdown-overrides div {
|
.markdown-overrides div {
|
||||||
@apply text-ch-gray-300 bg-transparent;
|
@apply text-ch-gray-300 bg-transparent;
|
||||||
}
|
}
|
||||||
|
.markdown-overrides a,
|
||||||
.markdown-overrides div a {
|
.markdown-overrides div a {
|
||||||
@apply text-ch-pink-500 underline bg-transparent;
|
@apply text-ch-pink-500 underline bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-overrides h3,
|
||||||
.markdown-overrides div h3 {
|
.markdown-overrides div h3 {
|
||||||
@apply text-xl;
|
@apply text-xl;
|
||||||
}
|
}
|
||||||
|
.markdown-overrides h2,
|
||||||
.markdown-overrides div h2 {
|
.markdown-overrides div h2 {
|
||||||
@apply text-2xl;
|
@apply text-2xl;
|
||||||
}
|
}
|
||||||
|
.markdown-overrides h1,
|
||||||
.markdown-overrides div h1 {
|
.markdown-overrides div h1 {
|
||||||
@apply text-3xl;
|
@apply text-3xl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15425,7 +15425,7 @@ raw-body@2.4.0:
|
|||||||
|
|
||||||
raw-loader@^4.0.2:
|
raw-loader@^4.0.2:
|
||||||
version "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==
|
integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
|
||||||
dependencies:
|
dependencies:
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user