15 Commits

Author SHA1 Message Date
Jerry 35e18ca956 Merge remote-tracking branch 'origin/main' into codex/simplecadapi-2.0.2-integration 2026-08-03 11:17:11 +08:00
Jerry c3a0f269b7 feat: integrate SimpleCADAPI 2.0.2 CAD workflows 2026-08-03 11:17:05 +08:00
likang 4572803e72 修改dockerfile 2026-07-28 15:24:43 +08:00
likang 51bb84c378 修改dockerfile 2026-07-28 15:01:24 +08:00
likang 2cca8fcb7d merge 2026-07-28 14:56:23 +08:00
Jerry b5738e9109 feat: unify CAD workflows on DesignIR 3.0 2026-07-28 14:41:26 +08:00
Jerry 6e33bc0e11 docs: add comprehensive Chinese project guide 2026-07-27 17:52:42 +08:00
Jerry c8dbc51549 refactor: simplify distillation data layout 2026-07-27 17:40:16 +08:00
Jerry 8895178844 docs: refresh project architecture and repository rules 2026-07-27 17:31:21 +08:00
Jerry 550b339bcc refactor: add DesignIR distillation pipeline 2026-07-27 17:24:04 +08:00
likang 0adaac2b2a merge 2026-07-27 16:01:32 +08:00
likang 9e74a5254a merge 2026-07-27 16:00:36 +08:00
Jerry 7b136b8562 7.27 2026-07-27 14:47:38 +08:00
Jerry 2edf8fb4c8 feat: strengthen CAD experience distillation and routing 2026-07-23 20:03:26 +08:00
Jerry a2f3ba6a90 feat: add per-part CAD model specs 2026-07-23 15:12:28 +08:00
1533 changed files with 186382 additions and 403701 deletions
+6
View File
@@ -0,0 +1,6 @@
# Normalize source and configuration files across operating systems.
* text=auto eol=lf
# STEP is an exchange artifact, not reviewable line-oriented source.
*.step binary
*.stp binary
+19 -22
View File
@@ -1,5 +1,9 @@
# Operating-system files
# Operating-system and editor metadata
.DS_Store
**/.idea/
**/.vscode/
*.swp
*.tmp
# Python environments and caches
**/.venv/
@@ -9,31 +13,24 @@
**/.mypy_cache/
*.py[cod]
# JavaScript dependencies and generated caches
# JavaScript dependencies and generated builds
**/node_modules/
**/.next/
**/.cache/
**/dist/
# Undownloaded upstream Git LFS demo/catalog pointers
# Local frontend runtime data
cad-agent-studio/config/llm.config.yaml
cad-agent-studio/data/
cad-agent-studio/models/
# Generated and upstream-heavy CAD assets
/models/
text-to-cad/assets/
text-to-cad/benchmarks/*.gif
text-to-cad/models/benchmarks/
text-to-cad/models/fun/
text-to-cad/models/mechanisms/
text-to-cad/models/robots/
text-to-cad/models/simple/
text-to-cad/models/
# Local editors and temporary files
**/.idea/
**/.vscode/
*.swp
*.tmp
# Private STEP evidence and local induction run reports
cad-experience-plugin/work/
# STEP inbox and private per-part reconstruction JSON
cad-experience-plugin/parser/input/*
!cad-experience-plugin/parser/input/.gitkeep
cad-experience-plugin/parser/output/*
!cad-experience-plugin/parser/output/.gitkeep
# Local distillation inputs and run artifacts. Only output/ is versioned.
designir-pipeline/input/*
!designir-pipeline/input/.gitkeep
designir-pipeline/runs/
@@ -1,94 +0,0 @@
---
alwaysApply: true
---
# Backend Architecture - Supabase
## Overview
The backend is built on Supabase, providing PostgreSQL database, authentication, and Edge Functions for serverless API endpoints.
## Database Structure
### Core Tables
- `conversations`: Chat conversations between users and AI
- `messages`: Individual messages within conversations
### Key Relationships
- Users have many conversations
- Conversations have many messages
- Users have one subscription
## Edge Functions (`supabase/functions/`)
### Authentication & User Management
- User registration and login handled by Supabase Auth
- JWT tokens for API authentication
### Core Functions
#### Chat Functions
- `chat/`: Parametric AI generation chat
### Shared Utilities (`_shared/`)
- `cors.ts`: Simple file with cors headers
- `messageUtils.ts`: Utility functions for formating user messages
- `parseParameter.ts`: Parameter parsing utilities
- `supabaseClient.ts`: Functions for getting supabase client
## API Patterns
### Authentication
- All sensitive functions require JWT authentication
- Use Supabase client for user verification
- Implement proper role-based access control
### Error Handling
- Consistent error response format
- Proper HTTP status codes
- Detailed error messages for debugging
### CORS Configuration
- Configured for frontend domain
- Handle preflight requests
- Support for development and production
### Environment Variables
- Stored in `supabase/functions/.env`
- Include API keys for external services
- Environment-specific configurations
### Configuration
- New functions should be put in config.toml
- Persistent storage buckets can be put either in a migration or in config.toml
## External Service Integration
### AI Services
- **Anthropic Claude**: Text generation and chat
## Database Migrations
### Migration Files
- Located in `supabase/migrations/`
- Version-controlled database schema changes
- Include both schema and data migrations
### Migration Patterns
- Use descriptive migration names
- Test migrations in development first
## Development Workflow
### Local Development
- Use `supabase start` for local database
- `supabase functions serve` for local functions
- ngrok so model can access
### Testing
- Test functions locally before deployment
- Use Supabase CLI for database operations
- Validate webhook endpoints
### Deployment
- Functions deployed via Supabase CLI
- Database migrations applied automatically
- Environment variables configured in Supabase dashboard
-40
View File
@@ -1,40 +0,0 @@
---
description:
globs:
alwaysApply: true
---
# Code Style Conventions
version: 1.0.0
## TypeScript Rules
### Naming Conventions
- Variables: `^[a-z][a-zA-Z0-9]*$`
- Functions: `^[a-z][a-zA-Z0-9]*$`
- Classes: `^[A-Z][a-zA-Z0-9]*$`
- Interfaces: `^[A-Z][a-zA-Z0-9]*$`
- Types: `^[A-Z][a-zA-Z0-9]*$`
### Formatting
- Max line length: 100 characters
- Indentation: 2 spaces
- Semicolons: required
- Quotes: single quotes
## React Rules
- Component naming: `^[A-Z][a-zA-Z0-9]*$`
- File naming: `^[A-Z][a-zA-Z0-9]*\.tsx$`
- Props interface: `^[A-Z][a-zA-Z0-9]*Props$`
## Import Rules
### Order
1. react
2. external-libraries
3. components
4. hooks
5. utils
6. types
7. styles
### Grouping
- Use newlines between import groups
-78
View File
@@ -1,78 +0,0 @@
---
description:
globs: *.sql,supabase.ts,supabase/*
alwaysApply: false
---
# Database Workflow Rules
version: 1.0.0
## Database Schema Changes
### NEVER Create Manual Migrations
- Do NOT manually create migration files in `supabase/migrations/`
- Always use the declarative schema approach
- Update schema files in `supabase/schemas/` folder instead
- Only modify generated migrations files if absolutely necessary
### Schema File Management
- Modify existing table schemas in `supabase/schemas/` files
- Add new tables by creating new schema files
- Follow the existing schema file patterns and naming conventions
## Migration Generation Process
### Step 1: Stop Supabase
```bash
supabase stop
```
### Step 2: Generate Migration
```bash
supabase db diff -f <migration_name>
```
- Replace `<migration_name>` with descriptive name (e.g., `add_user_preferences`, `create_new_table`)
- Migration will be generated in `supabase/migrations/` with timestamp
### Step 3: Apply Migration
```bash
supabase start && supabase migration up
```
## Local Development Only
### CRITICAL: Never Push to Remote
- NEVER use `supabase db push`
- NEVER use `supabase db pull`
- ALL migrations should be tested locally only
- Remote database changes are forbidden
## Type Generation
### Auto-Generate Types
- NEVER manually edit `shared/database.ts`
- Always regenerate after schema changes:
```bash
supabase gen types typescript --local > shared/database.ts
```
## Workflow Summary
1. **Edit Schema**: Modify `supabase/schemas/` files
2. **Stop Database**: `supabase stop`
3. **Generate Migration**: `supabase db diff -f <name>`
4. **Apply Migration**: `supabase start && supabase migration up`
5. **Update Types**: `supabase gen types typescript --local > shared/database.ts`
## Common Commands Reference
```bash
# Development workflow
supabase stop
supabase db diff -f <migration_name>
supabase start && supabase migration up
supabase gen types typescript --local > shared/database.ts
# NEVER use these commands
# supabase db push ❌
# supabase db pull ❌
```
@@ -1,39 +0,0 @@
---
globs: supabase/**/*
alwaysApply: false
---
# Deployment Restrictions
version: 1.0.0
## Function Deployment
### NEVER Deploy Functions
- **NEVER** run `supabase functions deploy` commands
- **NEVER** run `npx supabase functions deploy` commands
- **NEVER** attempt to deploy any Supabase Edge Functions
- **NEVER** suggest or recommend deploying functions
### Local Development Only
- All function testing should be done locally with `supabase functions serve`
- Use local Supabase instance for development and testing
- Function deployment is exclusively the responsibility of the human developer
### Code Changes Only
- AI assistant should only:
- Modify function code
- Update type definitions
- Make local code changes
- Run type checks
- Test locally if needed
### Deployment Handoff
- After making function changes, simply inform the user that:
- The code changes are complete
- Function deployment is required for the changes to take effect
- The user needs to deploy manually when ready
## Rationale
- Production deployments require human oversight
- Deployment credentials should not be accessed by AI
- Local testing is sufficient for development workflow
- Human developer maintains control over when and what gets deployed
@@ -1,97 +0,0 @@
---
alwaysApply: true
---
# Frontend Architecture - React/TypeScript
## Component Architecture
### Views (Page Components)
- Located in `src/views/`
- Each view represents a full page/route
- Use React Router for navigation
- Implement proper loading and error states
- Examples: `PromptView`, `EditorView`, `HistoryView`
### Reusable Components
- Located in `src/components/`
- Organized by feature/domain
- Use TypeScript interfaces for props
- Follow shadcn/ui patterns
- Implement proper accessibility
### Component Categories
- `ui/`: Base UI components (buttons, inputs, etc.)
- `chat/`: Components used in ChatSection
- `parameter/`: Components used in ParameterSection
- `viewer/`: Components used in ViewerSection
- `history/`: Components used in the history page
## State Management
### Contexts (`src/contexts/`)
- `AuthContext`: User authentication and session
- `BlobContext`: STL blob generated from OpenSCAD WASM
- `ColorContext`: Purely stylistic color for model
- `CurrentMessageContext`: Current message being processed
- `SelectedItemsContext`: Selected items across components
### React Query
- Used for server state management
- Configured in `src/main.tsx`
- Provides caching, synchronization, and background updates
- Handle loading, error, and success states
## Custom Hooks (`src/hooks/`)
- `useOpenSCAD`: OpenSCAD integration
- `useItemSelection`: Item selection management
- `useToast`: Toast notification management
## Services (`src/services/`)
- `conversationService`: Chat/conversation API calls, mutations and queries
- `messageService`: Message handling and processing, mutations and queries
- All services use Supabase client for API calls
## Utilities (`src/utils/`)
- `file-utils`: Primarily for getting a safe filename when exporting a file
- `parameterUtils`: Parameter validation and processing
- `downloadUtils`: Functions for downloading generated model
## Types (`src/types/`)
- `misc.ts`: Miscellaneous type definitions, should probably get broken up in the future
- Use proper TypeScript interfaces and types
- Export types for reuse across components
## 3D Graphics Integration
### Three.js Setup
- Use React Three Fiber for React integration
- Implement proper cleanup and resource management
- Handle WebGL context loss gracefully
- Use proper lighting and materials
### OpenSCAD Integration
- Web Worker in `src/worker/` for OpenSCAD processing
- WASM-based OpenSCAD compilation
- Real-time parameter updates
- Error handling for compilation failures
## Routing Structure
- Main routes defined in `src/main.tsx`
- Error boundaries for route error handling
## Styling
- Tailwind CSS for utility-first styling
- shadcn/ui components for consistent design
- Custom CSS in `src/index.css`
- Not standard css classes, adam specific, refer to tailwind.config.js for specifics
## Error Handling
- Error boundaries for component error catching
- Toast notifications for user feedback
- Proper error states in components
## Performance
- React Query for efficient data fetching
- Proper memoization with `useMemo` and `useCallback`
- Lazy loading for route components
- Image optimization and lazy loading
-93
View File
@@ -1,93 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Adam - AI-Powered 3D CAD Model Generation Platform
## Project Overview
Adam is a web application that enables users to generate 3D CAD models through AI-powered natural language, images, and direct manipulation. The platform combines parametric modeling with creative AI generation.
## Tech Stack
- **Frontend**: React 19 + TypeScript + Vite
- **UI Framework**: Radix UI + Tailwind CSS + shadcn/ui
- **3D Graphics**: Three.js + React Three Fiber
- **Backend**: Supabase (PostgreSQL + Edge Functions)
- **Authentication**: Supabase Auth
- **AI Services**: Anthropic Claude
- **State Management**: React Query + Context API
- **Routing**: React Router v6
## Project Structure
```
adam/
├── src/ # Frontend source code
│ ├── components/ # Reusable UI components
│ ├── views/ # Page-level components
│ ├── contexts/ # React contexts
│ ├── hooks/ # Custom React hooks
│ ├── services/ # API service functions
│ ├── utils/ # Utility functions
│ ├── types/ # TypeScript type definitions
│ ├── lib/ # Third-party library configurations
│ └── worker/ # Web Worker for OpenSCAD processing
├── supabase/ # Backend configuration
│ ├── functions/ # Edge functions
│ ├── migrations/ # Database migrations
│ ├── schemas/ # Database schemas
│ └── config.toml # Supabase configuration
├── public/ # Static assets
└── shared/ # Items shared between backend and frontend
```
## Development Conventions
### Code Style
- Use TypeScript for all new code
- Follow React 19 patterns and hooks
- Use functional components with hooks
- Implement proper error boundaries
- Use React Query for server state management
### Component Structure
- Components in `src/components/` are reusable
- Views in `src/views/` are page-level components
- Use proper TypeScript interfaces for props
- Implement proper loading and error states
### State Management
- Use React Context for global state (auth, user data)
- Use React Query for server state
- Use local state for component-specific data
- Implement proper loading states and error handling
### API Integration
- All API calls go through Supabase Edge Functions
- Use React Query for caching and synchronization
- Implement proper error handling and retry logic
- Use TypeScript interfaces for API responses.
### 3D Graphics
- Use Three.js for 3D rendering
- Implement proper cleanup for Three.js resources
- Use React Three Fiber for React integration
- Handle WebGL context loss gracefully
### Security
- All sensitive operations go through authenticated Edge Functions
- Implement proper CORS policies
- Validate all user inputs
- Use environment variables for sensitive data
## Environment Setup
- Frontend: `.env.local` for Vite environment variables
- Backend: `supabase/functions/.env` for Edge Function environment variables
- Use ngrok for local webhook development
## Common Patterns
- Use React Query for data fetching and caching
- Implement proper loading states with skeleton components
- Use toast notifications for user feedback
- Implement proper error boundaries
- Use React Router for navigation
- Follow the established component hierarchy
-119
View File
@@ -1,119 +0,0 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
# TypeScript Workflow Rules
version: 1.1.0
## Type Checking
### Always Run Type Check After Type Changes
- When modifying database schemas that affect TypeScript types
- When updating type definitions or interfaces
- When there are potential type conflicts or errors
- When the LLM cannot see all related files that might be affected
### Type Check Commands
#### Frontend (Node.js/TypeScript)
For files in `src/`, `shared/`, and other frontend directories:
```bash
npm run typecheck
```
#### Supabase Edge Functions (Deno)
For files in `supabase/functions/`, you must cd into each function directory:
```bash
cd supabase/functions/[function-name]
deno check index.ts
```
Example:
```bash
cd supabase/functions/chat
deno check index.ts
```
You can also run the npm command
```bash
npm run lint:supabase
```
## When to Run Type Check
### Database Schema Changes
- After running `supabase gen types typescript --local`
- After applying migrations that change table structures
- When adding new columns, tables, or modifying existing ones
### Type Definition Updates
- After modifying `src/types/` or `shared/*.ts` files
- When updating component prop interfaces
- When changing API response types
- When modifying context or hook return types
### Potential Type Issues
- When using `any` types as workarounds
- When there are TypeScript errors in the editor
- When importing/exporting between files with type dependencies
- When the LLM cannot see all related files in the conversation
## Workflow Integration
### With Database Changes
1. Edit schema files in `supabase/schemas/`
2. Generate migration: `supabase db diff -f <name>`
3. Apply migration: `supabase start && supabase migration up`
4. Regenerate types: `supabase gen types typescript --local > shared/database.ts`
5. **Run type check**:
- Frontend: `npx tsc -b`
- Edge Functions: `cd supabase/functions/[function-name] && deno check index.ts`
6. Fix any type errors in the codebase
### With Frontend Changes
1. Modify TypeScript files in `src/` or `shared/`
2. **Run type check**: `npx tsc -b`
3. Fix any type errors
4. Continue with implementation
### With Supabase Edge Function Changes
1. Modify TypeScript files in `supabase/functions/`
2. **Run type check**: `cd supabase/functions/[function-name] && deno check index.ts`
3. Fix any type errors
4. Continue with implementation
## Error Resolution
### Common Type Issues
- Missing properties in interfaces
- Incorrect return types from functions
- Type mismatches between components
- Missing imports for type definitions
### Resolution Steps
1. Run appropriate type check command based on file location:
- Frontend: `npx tsc -b`
- Edge Functions: `cd supabase/functions/[function-name] && deno check index.ts`
2. Fix errors systematically, starting with the most critical
3. Re-run type check after each fix
4. Ensure all type errors are resolved before proceeding
## Best Practices
### Type Safety
- Avoid using `any` types unless absolutely necessary
- Use proper TypeScript interfaces and types
- Leverage the generated Supabase types
- Maintain type consistency across the codebase
### Development Workflow
- Run type checks frequently during development
- Fix type errors immediately when they appear
- Use TypeScript strict mode settings
- Keep type definitions up to date with schema changes
### Environment-Specific Considerations
- **Frontend**: Uses Node.js TypeScript compiler, supports all standard TypeScript features
- **Edge Functions**: Uses Deno's TypeScript compiler, may have different import/export requirements
- **Shared Types**: Files in `shared/` are used by both environments, ensure compatibility
-36
View File
@@ -1,36 +0,0 @@
VITE_SUPABASE_ANON_KEY="<Test Anon Key>"
VITE_SUPABASE_URL='http://127.0.0.1:54321'
SUPABASE_SERVICE_ROLE_KEY="<Test Service Role Key>"
# Optional: SSO-only mode. Set to any Supabase OAuth provider slug — e.g.
# 'custom:my-idp' for a custom OIDC provider configured in your Supabase
# dashboard — and the app root becomes the only auth surface: its signed-out
# UI is unchanged, but the sign-in/sign-up affordances redirect to the
# provider, /signin & /signup & /signup-email bounce to root, and
# unauthenticated deep links redirect straight to the provider. Leave unset
# to keep the native auth UI (the local Supabase CLI stack can't host
# custom OIDC providers).
# VITE_SSO_PROVIDER="custom:my-idp"
# Optional: when SSO owns the identity, point profile / password / delete
# management at the provider's account page (the accounts.google.com model)
# instead of editing them in-app. Unset keeps the native in-app controls.
# VITE_ACCOUNT_URL="https://accounts.example.com/account"
VITE_POSTHOG_PROJECT_KEY="<Test PostHog Project Key>"
VITE_SENTRY_ENVIRONMENT="local"
VITE_SENTRY_DSN="<Sentry DSN>"
ANTHROPIC_API_KEY="<Test Anthropic API Key>"
OPENROUTER_API_KEY="<Test OpenRouter API Key>"
OPENAI_API_KEY="<Test OpenAI API Key>"
GOOGLE_API_KEY="<Test Google API Key>"
FAL_KEY="<Test FAL API Key>"
BILLING_SERVICE_URL="<Test Billing Service URL>"
BILLING_SERVICE_KEY="<Test Billing Service Key>"
ENVIRONMENT="local"
ADAM_URL="<Adam URL or dev URL>"
WEBHOOK_BASE_URL="<Public TanStack App URL>"
NGROK_URL="<NGROK URL>"
# Shared bearer secret for the internal server-to-server account-purge endpoint
# (POST /api/internal/account/delete). The upstream identity provider's purge
# worker presents this to erase a user's data here when their account is
# deleted. Leave unset to hard-disable the endpoint (it 503s every request).
# Provider-agnostic: any deployment sets its own value.
ACCOUNT_PURGE_SECRET="<Shared account-purge secret>"
-35
View File
@@ -1,35 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
.output
,output
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.env
.vercel
# other secret files
.env.local
.env.langfuse
-2
View File
@@ -1,2 +0,0 @@
npx tsc -b
npx lint-staged
-1
View File
@@ -1 +0,0 @@
@jsr:registry=https://npm.jsr.io
-2
View File
@@ -1,2 +0,0 @@
src/vendor/openscad-wasm/
src/assets/
-4
View File
@@ -1,4 +0,0 @@
{
"singleQuote": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
-51
View File
@@ -1,51 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement by emailing zach@adam.new. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
-25
View File
@@ -1,25 +0,0 @@
# Contributing to CADAM
When contributing to this repository, please first discuss the change you wish to make via [issues](https://github.com/Adam-CAD/CADAM/issues) before making a change.
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Pull Request Process
1. **Fork the project** - Create a fork of the repository to your own GitHub account.
![Fork](https://docs.github.com/assets/cb-40742/mw-1440/images/help/repository/fork-button.webp)
2. **Create your changes** - Make your changes in your fork and open a PR from that fork.
3. **Update the PR description** - Include details of the changes. Link the issue if relevant.
4. **Allow maintainer edits** - Be sure to check the box to "Allow edits from maintainer". This allows maintainers to update your PR if necessary which speeds up the review process. [Learn more here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
5. **Request a review** - Request a review from one of the maintainers. Once accepted, they will be able to merge your PR.
## Style Guide
We try to follow guidelines from [Clean Code](https://www.oreilly.com/library/view/clean-code-a/9780136083238/) and the Boy Scout Rule:
> "Leave the code cleaner, not messier, than how you found it."
-185
View File
@@ -1,185 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
-318
View File
@@ -1,318 +0,0 @@
<div align="center">
<a href="https://adam.new/cadam">
<img src="./public/cadam-launch.gif" alt="CADAM — text-to-CAD live demo" width="100%">
</a>
</div>
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./public/Github-Banner-Dark.png">
<source media="(prefers-color-scheme: light)" srcset="./public/Github-Banner-Light.png">
<img src="./public/Github-Banner-Light.png" alt="CADAM Banner" width="100%"/>
</picture>
</div>
<h1 align="center"> ⛮ The Open Source Text to CAD Web App ⛮ </h1>
<div align="center">
[![Stars](https://img.shields.io/github/stars/Adam-CAD/cadam?style=social&logo=github)](https://github.com/Adam-CAD/cadam/stargazers)
[![Forks](https://img.shields.io/github/forks/Adam-CAD/CADAM?style=flat)](https://github.com/Adam-CAD/CADAM/network)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://www.gnu.org/licenses/gpl-3.0)
[![Node.js](https://img.shields.io/badge/Node.js-20.19%2B%20%7C%2022.12%2B-green.svg?style=flat&logo=node.js&logoColor=white)](https://nodejs.org/)
[![React](https://img.shields.io/badge/React-19.1-61DAFB.svg?style=flat&logo=react&logoColor=black)](https://reactjs.org/)
[![Supabase](https://img.shields.io/badge/Supabase-Backend-3ECF8E.svg?style=flat&logo=supabase&logoColor=white)](https://supabase.com/)
[![OpenSCAD](https://img.shields.io/badge/OpenSCAD-WASM-F9D64F.svg?style=flat)](https://openscad.org/)
[![Website](https://img.shields.io/badge/website-adam.new-blue?style=flat)](https://adam.new)
[![Discord](https://img.shields.io/badge/Discord-Join-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.com/invite/HKdXDqAHCs)
[![Follow Zach Dive](https://img.shields.io/badge/Follow-Zach%20Dive-1DA1F2?style=flat&logo=x&logoColor=white)](https://x.com/zachdive)
[![Follow Aaron Li](https://img.shields.io/badge/Follow-Aaron%20Li-1DA1F2?style=flat&logo=x&logoColor=white)](https://x.com/aaronhetengli)
[![Follow Dylan Anderson](https://img.shields.io/badge/Follow-tsadpbb-1DA1F2?style=flat&logo=x&logoColor=white)](https://x.com/tsadpbb)
</div>
---
## 🌐 Try it live
**👉 [adam.new/cadam](https://adam.new/cadam)**. Generate a CAD model in seconds, right in your browser. No install required.
## ✨ Features
- 🤖 **AI-Powered Generation** - Transform natural language and images into 3D models
- 🎛️ **Parametric Controls** - Interactive sliders for instant dimension adjustments
- 📦 **Multiple Export Formats** - Export as .STL, .SCAD, or .DXF files
- 🌐 **Browser-Based** - Runs entirely in your browser using WebAssembly
- 📚 **Library Support** - Includes BOSL, BOSL2, and MCAD libraries
## 🎯 Key Capabilities
| Feature | Description |
| -------------------------- | ---------------------------------------------------- |
| **Natural Language Input** | Describe your 3D model in plain English |
| **Image References** | Upload images to guide model generation |
| **Real-time Preview** | See your model update instantly with Three.js |
| **Parameter Extraction** | Automatically identifies adjustable dimensions |
| **Smart Updates** | Efficient parameter changes without AI re-generation |
| **Custom Fonts** | Built-in Geist font support for text in models |
## 📺 Screenshots
<img src="./public/screenshot-2.jpeg" alt="CADAM Screenshot 2" />
## 🧪 Benchmarks
A showcase of what CADAM builds from a single plain-language description — from full multi-part machines down to clean parametric parts. Each model below started from the prompt shown and came out as fully parametric OpenSCAD, ready to export as `.STL`, `.SCAD`, or `.DXF`. The source and a short write-up for each live in [`benchmarks/`](benchmarks/); the orbiting previews are rendered with [`benchmarks/render.sh`](benchmarks/render.sh).
### Complex machines & assemblies
<table>
<thead><tr><th>Model</th><th>Prompt</th><th>Controls</th><th>Output</th></tr></thead>
<tbody>
<tr>
<td><a href="benchmarks/13-v8-engine.md"><strong>V8 engine</strong></a></td>
<td>A complete V8 internal combustion engine: two banks of four cylinders in a 90° V, cylinder heads with ribbed valve covers, an intake manifold in the valley, exhaust headers down each bank, a crankshaft with counterweights, pistons and connecting rods, a front pulley, and an oil pan.</td>
<td>22 dims<br>8 colors</td>
<td><img src="benchmarks/13-v8-engine.gif" alt="V8 engine orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/10-radial-aircraft-engine.md"><strong>9-cylinder radial aircraft engine</strong></a></td>
<td>Design a 9-cylinder radial aircraft engine: a central round crankcase with nine finned cylinders arranged evenly in a star pattern around it, each cylinder with stacked cooling fins and a domed cylinder head, and a central propeller shaft hub at the front.</td>
<td>15 dims<br>6 colors</td>
<td><img src="benchmarks/10-radial-aircraft-engine.gif" alt="9-cylinder radial aircraft engine orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/11-turbofan-jet-engine.md"><strong>Turbofan jet engine</strong></a></td>
<td>A complete high-bypass turbofan: a front fan you can see into, a bypass cowl, an internal core with compressor/turbine stages, outlet guide vanes, and an exhaust plug.</td>
<td>2 dims<br>10 colors</td>
<td><img src="benchmarks/11-turbofan-jet-engine.gif" alt="Turbofan jet engine orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/12-axial-turbine-blisk.md"><strong>Axial turbine blisk</strong></a></td>
<td>Model an axial-flow turbine blisk (bladed disk) like a jet engine compressor stage: a central hub with a shaft bore and a single ring of about 28 thin aerofoil blades around the rim, each blade clearly twisted along its height from root to tip like a real turbine blade.</td>
<td>14 dims<br>1 color</td>
<td><img src="benchmarks/12-axial-turbine-blisk.gif" alt="Axial turbine blisk orbit" width="200"></td>
</tr>
</tbody>
</table>
### Parametric fundamentals
<table>
<thead><tr><th>Model</th><th>Prompt</th><th>Controls</th><th>Output</th></tr></thead>
<tbody>
<tr>
<td><a href="benchmarks/01-twisted-hex-vase.md"><strong>Twisted hexagonal vase</strong></a></td>
<td>Design a twisted hexagonal vase: a hollow shell about 150 mm tall that tapers from a 70 mm base to a 50 mm mouth, with the hexagonal cross-section twisting 120 degrees from bottom to top, a 2 mm wall, and a closed bottom.</td>
<td>6 dims<br>1 color</td>
<td><img src="benchmarks/01-twisted-hex-vase.gif" alt="Twisted hexagonal vase orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/02-knurled-control-knob.md"><strong>Knurled control knob</strong></a></td>
<td>Make a knurled control knob 40 mm in diameter and 22 mm tall with a diamond-knurled grip, a raised pointer mark on top, a 6 mm D-shaped shaft bore, and an M3 set-screw hole through the side.</td>
<td>15 dims<br>2 colors</td>
<td><img src="benchmarks/02-knurled-control-knob.gif" alt="Knurled control knob orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/03-hex-bolt-and-nut.md"><strong>Hex bolt &amp; nut — real threads</strong></a></td>
<td>Create an M12 hex bolt 45 mm long with a real threaded shaft and a standard hex head, plus its matching hex nut, placed side by side.</td>
<td>3 dims<br>2 colors</td>
<td><img src="benchmarks/03-hex-bolt-and-nut.gif" alt="Hex bolt &amp; nut — real threads orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/04-honeycomb-bracket.md"><strong>Honeycomb lightweight bracket</strong></a></td>
<td>Design a 90-degree angle mounting bracket with 80x80 mm flanges that are 5 mm thick, lightened with a hexagonal honeycomb cutout pattern on both faces, four M5 mounting holes, and a filleted inside corner.</td>
<td>13 dims<br>1 color</td>
<td><img src="benchmarks/04-honeycomb-bracket.gif" alt="Honeycomb lightweight bracket orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/05-naca-airfoil-wing.md"><strong>NACA 2412 tapered wing</strong></a></td>
<td>Model a tapered aircraft wing section using a real NACA 2412 airfoil: 120 mm root chord tapering to 80 mm tip over a 200 mm span, with two spanwise spar tubes and a few lightening holes.</td>
<td>9 dims<br>1 color</td>
<td><img src="benchmarks/05-naca-airfoil-wing.gif" alt="NACA 2412 tapered wing orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/06-threaded-jar-and-lid.md"><strong>Threaded jar &amp; screw-on lid</strong></a></td>
<td>Create a small storage jar with external screw threads at the neck and a matching screw-on lid with internal threads. Jar body 60 mm diameter, 70 mm tall, 2.5 mm walls; show the lid unscrewed and sitting beside the jar.</td>
<td>9 dims<br>2 colors</td>
<td><img src="benchmarks/06-threaded-jar-and-lid.gif" alt="Threaded jar &amp; screw-on lid orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/07-bevel-gear-drive.md"><strong>Right-angle bevel gear drive</strong></a></td>
<td>Build a right-angle bevel gear drive: a 24-tooth bevel gear on a vertical shaft meshing at 90 degrees with a 16-tooth bevel pinion on a horizontal shaft, each on a short stub shaft.</td>
<td>9 dims<br>3 colors</td>
<td><img src="benchmarks/07-bevel-gear-drive.gif" alt="Right-angle bevel gear drive orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/08-centrifugal-impeller.md"><strong>Centrifugal pump impeller</strong></a></td>
<td>Design a centrifugal pump impeller: a 90 mm diameter back-plate with a central 12 mm bore and a raised hub, and seven backward-curved blades that sweep from the hub out to the rim, each blade curving smoothly along its path.</td>
<td>10 dims<br>1 color</td>
<td><img src="benchmarks/08-centrifugal-impeller.gif" alt="Centrifugal pump impeller orbit" width="200"></td>
</tr>
<tr>
<td><a href="benchmarks/09-herringbone-planetary-gearbox.md"><strong>Herringbone planetary gear stage</strong></a></td>
<td>Model a herringbone planetary gear stage at module 1.5: a central sun gear with 18 teeth, three planet gears with 18 teeth each meshing around it, an internal ring gear with 54 teeth, and a carrier plate linking the three planet axles. Color the sun, planets, ring, and carrier differently.</td>
<td>10 dims<br>4 colors</td>
<td><img src="benchmarks/09-herringbone-planetary-gearbox.gif" alt="Herringbone planetary gear stage orbit" width="200"></td>
</tr>
</tbody>
</table>
## 🚀 Quick Start
```bash
# Clone the repository
git clone https://github.com/Adam-CAD/CADAM.git
cd CADAM
# Install dependencies
npm install
# Start Supabase
npx supabase start
npx supabase functions serve --no-verify-jwt
# Start the development server
npm run dev
```
## 📋 Prerequisites
- Node.js ^20.19.0 or >=22.12.0, with npm 10+
- Supabase CLI
- ngrok (for local webhook development)
## 🔧 Setting Up Environment Variables
### 1. Frontend Environment:
- Copy `.env.local.template` to `.env.local`
- Update all required keys in `.env.local`:
```
VITE_SUPABASE_ANON_KEY="<Test Anon Key>"
VITE_SUPABASE_URL='http://127.0.0.1:54321'
```
### 2. Server Environment:
- Add server-side keys to `.env.local`, including:
```
ANTHROPIC_API_KEY="<Test Anthropic API Key>"
OPENROUTER_API_KEY="<Test OpenRouter API Key>"
OPENAI_API_KEY="<Test OpenAI API Key>"
GOOGLE_API_KEY="<Test Google API Key>"
FAL_KEY="<Test FAL API Key>"
SUPABASE_SERVICE_ROLE_KEY="<Test Service Role Key>"
BILLING_SERVICE_URL="<Test Billing Service URL>"
BILLING_SERVICE_KEY="<Test Billing Service Key>"
ENVIRONMENT="local"
ADAM_URL="<Adam URL or dev URL>" # Checkout and portal redirect target
WEBHOOK_BASE_URL="<Public TanStack App URL>" # Your app URL for /cadam/api callbacks
NGROK_URL="<NGROK URL>" # Optional local Supabase Storage tunnel for provider-readable signed URLs
```
## 🌐 Setting Up ngrok for Local Development
CADAM uses public URLs for provider callbacks and local signed storage URLs:
1. Install ngrok if you haven't already:
```bash
npm install -g ngrok
# or
brew install ngrok
```
2. Start an ngrok tunnel pointing to your TanStack Start dev server:
```bash
ngrok http 3000
```
3. Copy the generated ngrok URL (e.g., https://xxxx-xx-xx-xxx-xx.ngrok.io) and add it to your `.env.local` file:
```
WEBHOOK_BASE_URL="https://xxxx-xx-xx-xxx-xx.ngrok.io"
```
4. If a provider must fetch local Supabase Storage signed URLs, run a second tunnel to Supabase and set `NGROK_URL` to that URL.
5. Ensure `ENVIRONMENT="local"` is set in the same file.
## 💻 Development Workflow
### Install Dependencies
```bash
npm i
```
### Start Supabase Services
```bash
npx supabase start
npm run dev
```
## 🛠️ Built With
- **Frontend:** React 19 + TypeScript + TanStack Start + Vite
- **3D Rendering:** Three.js + React Three Fiber
- **CAD Engine:** OpenSCAD WebAssembly
- **Backend:** TanStack Start server routes + Supabase PostgreSQL/Auth/Storage
- **AI:** Anthropic Claude API
- **Styling:** Tailwind CSS + shadcn/ui
- **Libraries:** BOSL, BOSL2, MCAD
## 🤝 Contributing
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also [open an issue](https://github.com/Adam-CAD/CADAM/issues).
See the [CONTRIBUTING.md](CONTRIBUTING.md) for instructions and [code of conduct](CODE_OF_CONDUCT.md).
## 🙏 Credits
This app wouldn't be possible without the work of:
- [OpenSCAD](https://github.com/openscad/openscad)
- [openscad-wasm](https://github.com/openscad/openscad-wasm)
- [openscad-playground](https://github.com/openscad/openscad-playground)
- [openscad-web-gui](https://github.com/seasick/openscad-web-gui)
- [dingcad](https://github.com/yacineMTB/dingcad)
## 📄 License
This distribution is licensed under the GNU General Public License v3.0 (GPLv3). See `LICENSE`.
Components and attributions:
- Portions of this project are derived from `openscad-web-gui` (GPLv3).
- This distribution includes unmodified binaries from OpenSCAD WASM under
GPL v2 or later; distributed here under GPLv3 as part of the combined work.
See `src/vendor/openscad-wasm/SOURCE-OFFER.txt`.
---
## 🌟 Star History
<div align="center">
<a href="https://www.repostars.dev/?repos=Adam-CAD%2FCADAM&theme=forest">
<img src="https://www.repostars.dev/api/embed?repo=Adam-CAD/CADAM&theme=forest" alt="CADAM Star History" width="700"/>
</a>
<sub>Live chart by <a href="https://www.repostars.dev/?repos=Adam-CAD%2FCADAM&theme=forest">RepoStars</a> — click for the interactive version.</sub>
</div>
---
<div align="center">
**⭐ If you find CADAM useful, please consider giving it a star!**
[![Stars](https://img.shields.io/github/stars/Adam-CAD/cadam?style=social&logo=github)](https://github.com/Adam-CAD/cadam/stargazers)
Made with 💙 for the 3D printing and CAD community
</div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 940 KiB

-97
View File
@@ -1,97 +0,0 @@
# 1 — Twisted hexagonal vase
> **Prompt**
>
> Design a twisted hexagonal vase: a hollow shell about 150 mm tall that tapers from a 70 mm base to a 50 mm mouth, with the hexagonal cross-section twisting 120 degrees from bottom to top, a 2 mm wall, and a closed bottom.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="01-twisted-hex-vase.gif" alt="Twisted hexagonal vase — orbiting render" width="380"></p>
**Parametric controls:** 6 dimensions · 1 colour
**What it demonstrates**
- Generative twist-loft via `linear_extrude(twist=, scale=)` over a hexagon
- Hollowed with a solid printable floor (inner cutter intersected with a lifted cylinder)
- Compensates the hexagon flat-angle so the wall thickness stays true on every face
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `height` | `150` | `[50:5:300]` | Total height of the vase |
| `base_radius` | `35` | `[20:1:100]` | Base radius (circumradius, so diameter is 2x this) |
| `mouth_radius` | `25` | `[10:1:80]` | Mouth radius (circumradius, so diameter is 2x this) |
| `twist_angle` | `120` | `[-720:5:720]` | Total twist angle from bottom to top (degrees) |
| `wall_thickness` | `2.0` | `[0.5:0.5:5.0]` | Thickness of the walls and base |
<details>
<summary>OpenSCAD source — <code>01-twisted-hex-vase.scad</code></summary>
```scad
// Twisted Hexagonal Vase
/* [Dimensions] */
// Total height of the vase
height = 150; // [50:5:300]
// Base radius (circumradius, so diameter is 2x this)
base_radius = 35; // [20:1:100]
// Mouth radius (circumradius, so diameter is 2x this)
mouth_radius = 25; // [10:1:80]
// Total twist angle from bottom to top (degrees)
twist_angle = 120; // [-720:5:720]
// Thickness of the walls and base
wall_thickness = 2.0; // [0.5:0.5:5.0]
/* [Style] */
// Color of the vase
vase_color = "Turquoise";
/* [Hidden] */
// Compensation for the hexagon's angle to maintain true wall thickness on the flats
wall_adj = wall_thickness / cos(30);
// Number of vertical slices for a smooth twist
slices = height * 2;
// Outer geometry variables
r_bot_out = base_radius;
r_top_out = mouth_radius;
scale_out = r_top_out / r_bot_out;
// Inner geometry variables
r_bot_in = base_radius - wall_adj;
r_top_in_at_H = mouth_radius - wall_adj;
// Extend the inner cutter slightly to ensure a clean opening at the top
cut_extra = 2;
H_in = height + cut_extra;
// Calculate slope of inner radius change to maintain perfect alignment
slope = (r_top_in_at_H - r_bot_in) / height;
r_top_in = r_bot_in + slope * H_in;
scale_in = r_top_in / r_bot_in;
twist_in = twist_angle * (H_in / height);
color(vase_color)
difference() {
// Outer Solid Shell
linear_extrude(height = height, twist = twist_angle, scale = scale_out, slices = slices)
circle(r = r_bot_out, $fn = 6);
// Inner Cut (Hollow)
// We intersect the twisted inner solid with a lifted cylinder
// to leave a solid floor of exactly `wall_thickness`.
intersection() {
linear_extrude(height = H_in, twist = twist_in, scale = scale_in, slices = slices)
circle(r = r_bot_in, $fn = 6);
translate([0, 0, wall_thickness])
cylinder(h = H_in, r = max(base_radius, mouth_radius) * 2, $fn = 32);
}
}
```
</details>
-62
View File
@@ -1,62 +0,0 @@
// Twisted Hexagonal Vase
/* [Dimensions] */
// Total height of the vase
height = 150; // [50:5:300]
// Base radius (circumradius, so diameter is 2x this)
base_radius = 35; // [20:1:100]
// Mouth radius (circumradius, so diameter is 2x this)
mouth_radius = 25; // [10:1:80]
// Total twist angle from bottom to top (degrees)
twist_angle = 120; // [-720:5:720]
// Thickness of the walls and base
wall_thickness = 2.0; // [0.5:0.5:5.0]
/* [Style] */
// Color of the vase
vase_color = "Turquoise";
/* [Hidden] */
// Compensation for the hexagon's angle to maintain true wall thickness on the flats
wall_adj = wall_thickness / cos(30);
// Number of vertical slices for a smooth twist
slices = height * 2;
// Outer geometry variables
r_bot_out = base_radius;
r_top_out = mouth_radius;
scale_out = r_top_out / r_bot_out;
// Inner geometry variables
r_bot_in = base_radius - wall_adj;
r_top_in_at_H = mouth_radius - wall_adj;
// Extend the inner cutter slightly to ensure a clean opening at the top
cut_extra = 2;
H_in = height + cut_extra;
// Calculate slope of inner radius change to maintain perfect alignment
slope = (r_top_in_at_H - r_bot_in) / height;
r_top_in = r_bot_in + slope * H_in;
scale_in = r_top_in / r_bot_in;
twist_in = twist_angle * (H_in / height);
color(vase_color)
difference() {
// Outer Solid Shell
linear_extrude(height = height, twist = twist_angle, scale = scale_out, slices = slices)
circle(r = r_bot_out, $fn = 6);
// Inner Cut (Hollow)
// We intersect the twisted inner solid with a lifted cylinder
// to leave a solid floor of exactly `wall_thickness`.
intersection() {
linear_extrude(height = H_in, twist = twist_in, scale = scale_in, slices = slices)
circle(r = r_bot_in, $fn = 6);
translate([0, 0, wall_thickness])
cylinder(h = H_in, r = max(base_radius, mouth_radius) * 2, $fn = 32);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 515 KiB

-172
View File
@@ -1,172 +0,0 @@
# 2 — Knurled control knob
> **Prompt**
>
> Make a knurled control knob 40 mm in diameter and 22 mm tall with a diamond-knurled grip, a raised pointer mark on top, a 6 mm D-shaped shaft bore, and an M3 set-screw hole through the side.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="02-knurled-control-knob.gif" alt="Knurled control knob — orbiting render" width="380"></p>
**Parametric controls:** 15 dimensions · 2 colours
**What it demonstrates**
- Diamond-knurled grip surface (BOSL2)
- D-profile shaft bore + M3 set-screw with counterbore
- Raised pointer indicator, modelled as a separate colour
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `knob_diameter` | `40` | `[20:1:80]` | Outer diameter of the knob |
| `knob_height` | `22` | `[10:1:50]` | Total height of the knob |
| `knurl_density` | `36` | `[16:2:100]` | Number of knurl ridges |
| `knurl_depth` | `1.2` | `[0.5:0.1:3.0]` | Depth of the diamond knurl pattern |
| `shaft_diameter` | `6.0` | `[3:0.1:12]` | Diameter of the D-shaft |
| `shaft_clearance` | `0.2` | `[0:0.05:0.5]` | Extra clearance for easy fit |
| `shaft_flat_depth` | `1.5` | `[0:0.1:4.0]` | Depth of the flat portion on the D-shaft |
| `shaft_bore_depth` | `18` | `[5:1:40]` | How deep the shaft hole goes into the knob |
| `set_screw_diameter` | `3.0` | `[2:0.1:6.0]` | Thread diameter for the set screw (M3 = 3.0) |
| `set_screw_height` | `8` | `[2:1:30]` | Height of the set screw hole from the bottom |
| `set_screw_cb_diameter` | `6.0` | `[3:0.1:10.0]` | Diameter of the counterbore for the set screw head |
| `set_screw_cb_depth` | `12` | `[0:1:30]` | Depth of the counterbore |
| `pointer_angle` | `180` | `[0:15:345]` | Rotational angle of the pointer (180 points opposite to flat) |
| `pointer_width` | `4` | `[2:0.5:10]` | Width of the pointer base |
| … | | | _+1 more_ |
<details>
<summary>OpenSCAD source — <code>02-knurled-control-knob.scad</code></summary>
```scad
include <BOSL2/std.scad>
/* [Dimensions] */
// Outer diameter of the knob
knob_diameter = 40; // [20:1:80]
// Total height of the knob
knob_height = 22; // [10:1:50]
// Number of knurl ridges
knurl_density = 36; // [16:2:100]
// Depth of the diamond knurl pattern
knurl_depth = 1.2; // [0.5:0.1:3.0]
/* [Shaft Interface] */
// Diameter of the D-shaft
shaft_diameter = 6.0; // [3:0.1:12]
// Extra clearance for easy fit
shaft_clearance = 0.2; // [0:0.05:0.5]
// Depth of the flat portion on the D-shaft
shaft_flat_depth = 1.5; // [0:0.1:4.0]
// How deep the shaft hole goes into the knob
shaft_bore_depth = 18; // [5:1:40]
/* [Set Screw] */
// Thread diameter for the set screw (M3 = 3.0)
set_screw_diameter = 3.0; // [2:0.1:6.0]
// Height of the set screw hole from the bottom
set_screw_height = 8; // [2:1:30]
// Diameter of the counterbore for the set screw head
set_screw_cb_diameter = 6.0; // [3:0.1:10.0]
// Depth of the counterbore
set_screw_cb_depth = 12; // [0:1:30]
/* [Pointer] */
// Rotational angle of the pointer (180 points opposite to flat)
pointer_angle = 180; // [0:15:345]
// Width of the pointer base
pointer_width = 4; // [2:0.5:10]
// Raised height of the pointer
pointer_height = 1.5; // [0.5:0.1:5]
/* [Colors] */
knob_color = "DarkSlateGray";
pointer_color = "Silver";
// 2D gear profile for sweeping into knurls
module gear_2d(r, depth, teeth) {
w = ((r - depth) * 2 * 3.14159) / teeth * 1.1;
union() {
circle(r = r - depth, $fn=120);
for(i=[0:teeth-1]) {
rotate([0, 0, i * 360 / teeth])
polygon([
[r - depth + 0.05, -w/2],
[r - depth + 0.05, w/2],
[r, 0]
]);
}
}
}
// Cylindrical base with top and bottom chamfers
module chamfered_cylinder(d, h, chamfer) {
rotate_extrude($fn=120) {
polygon([
[0, 0],
[d/2 - chamfer, 0],
[d/2, chamfer],
[d/2, h - chamfer],
[d/2 - chamfer, h],
[0, h]
]);
}
}
// D-shaft boolean cutter
module d_shaft_cutter(d, depth, flat_dist) {
intersection() {
cylinder(d=d, h=depth, $fn=64);
translate([-d, -d, 0])
cube([d*2, d + flat_dist, depth]);
}
}
// Variables derived from parameters
actual_shaft_d = shaft_diameter + shaft_clearance;
flat_dist = (shaft_diameter/2) - shaft_flat_depth + (shaft_clearance/2);
// Main Assembly
difference() {
// Solid body with diamond knurling
color(knob_color)
intersection() {
twist_angle = 360 * knob_height / (3.14159 * knob_diameter);
intersection() {
linear_extrude(height=knob_height, twist=twist_angle, slices=60, convexity=5)
gear_2d(knob_diameter/2, knurl_depth, knurl_density);
linear_extrude(height=knob_height, twist=-twist_angle, slices=60, convexity=5)
gear_2d(knob_diameter/2, knurl_depth, knurl_density);
}
chamfered_cylinder(knob_diameter, knob_height, 1.5);
}
// D-Shaft Cut
translate([0, 0, -0.05])
d_shaft_cutter(actual_shaft_d, shaft_bore_depth + 0.05, flat_dist);
// Set Screw Cut
translate([0, knob_diameter/2 + 1, set_screw_height])
rotate([90, 0, 0]) {
cylinder(d=set_screw_diameter, h=knob_diameter/2 + 2, $fn=32);
translate([0, 0, -1])
cylinder(d=set_screw_cb_diameter, h=set_screw_cb_depth + 1, $fn=32);
}
}
// Top Pointer Indicator
color(pointer_color)
rotate([0, 0, pointer_angle])
translate([0, 0, knob_height - 0.05])
hull() {
// Outer tip
translate([0, knob_diameter/2 - 4, 0])
cylinder(d=pointer_width/2, h=pointer_height, $fn=32);
// Inner base
translate([0, 4, 0])
cylinder(d=pointer_width, h=pointer_height, $fn=32);
}
```
</details>
@@ -1,127 +0,0 @@
include <BOSL2/std.scad>
/* [Dimensions] */
// Outer diameter of the knob
knob_diameter = 40; // [20:1:80]
// Total height of the knob
knob_height = 22; // [10:1:50]
// Number of knurl ridges
knurl_density = 36; // [16:2:100]
// Depth of the diamond knurl pattern
knurl_depth = 1.2; // [0.5:0.1:3.0]
/* [Shaft Interface] */
// Diameter of the D-shaft
shaft_diameter = 6.0; // [3:0.1:12]
// Extra clearance for easy fit
shaft_clearance = 0.2; // [0:0.05:0.5]
// Depth of the flat portion on the D-shaft
shaft_flat_depth = 1.5; // [0:0.1:4.0]
// How deep the shaft hole goes into the knob
shaft_bore_depth = 18; // [5:1:40]
/* [Set Screw] */
// Thread diameter for the set screw (M3 = 3.0)
set_screw_diameter = 3.0; // [2:0.1:6.0]
// Height of the set screw hole from the bottom
set_screw_height = 8; // [2:1:30]
// Diameter of the counterbore for the set screw head
set_screw_cb_diameter = 6.0; // [3:0.1:10.0]
// Depth of the counterbore
set_screw_cb_depth = 12; // [0:1:30]
/* [Pointer] */
// Rotational angle of the pointer (180 points opposite to flat)
pointer_angle = 180; // [0:15:345]
// Width of the pointer base
pointer_width = 4; // [2:0.5:10]
// Raised height of the pointer
pointer_height = 1.5; // [0.5:0.1:5]
/* [Colors] */
knob_color = "DarkSlateGray";
pointer_color = "Silver";
// 2D gear profile for sweeping into knurls
module gear_2d(r, depth, teeth) {
w = ((r - depth) * 2 * 3.14159) / teeth * 1.1;
union() {
circle(r = r - depth, $fn=120);
for(i=[0:teeth-1]) {
rotate([0, 0, i * 360 / teeth])
polygon([
[r - depth + 0.05, -w/2],
[r - depth + 0.05, w/2],
[r, 0]
]);
}
}
}
// Cylindrical base with top and bottom chamfers
module chamfered_cylinder(d, h, chamfer) {
rotate_extrude($fn=120) {
polygon([
[0, 0],
[d/2 - chamfer, 0],
[d/2, chamfer],
[d/2, h - chamfer],
[d/2 - chamfer, h],
[0, h]
]);
}
}
// D-shaft boolean cutter
module d_shaft_cutter(d, depth, flat_dist) {
intersection() {
cylinder(d=d, h=depth, $fn=64);
translate([-d, -d, 0])
cube([d*2, d + flat_dist, depth]);
}
}
// Variables derived from parameters
actual_shaft_d = shaft_diameter + shaft_clearance;
flat_dist = (shaft_diameter/2) - shaft_flat_depth + (shaft_clearance/2);
// Main Assembly
difference() {
// Solid body with diamond knurling
color(knob_color)
intersection() {
twist_angle = 360 * knob_height / (3.14159 * knob_diameter);
intersection() {
linear_extrude(height=knob_height, twist=twist_angle, slices=60, convexity=5)
gear_2d(knob_diameter/2, knurl_depth, knurl_density);
linear_extrude(height=knob_height, twist=-twist_angle, slices=60, convexity=5)
gear_2d(knob_diameter/2, knurl_depth, knurl_density);
}
chamfered_cylinder(knob_diameter, knob_height, 1.5);
}
// D-Shaft Cut
translate([0, 0, -0.05])
d_shaft_cutter(actual_shaft_d, shaft_bore_depth + 0.05, flat_dist);
// Set Screw Cut
translate([0, knob_diameter/2 + 1, set_screw_height])
rotate([90, 0, 0]) {
cylinder(d=set_screw_diameter, h=knob_diameter/2 + 2, $fn=32);
translate([0, 0, -1])
cylinder(d=set_screw_cb_diameter, h=set_screw_cb_depth + 1, $fn=32);
}
}
// Top Pointer Indicator
color(pointer_color)
rotate([0, 0, pointer_angle])
translate([0, 0, knob_height - 0.05])
hull() {
// Outer tip
translate([0, knob_diameter/2 - 4, 0])
cylinder(d=pointer_width/2, h=pointer_height, $fn=32);
// Inner base
translate([0, 4, 0])
cylinder(d=pointer_width, h=pointer_height, $fn=32);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

-56
View File
@@ -1,56 +0,0 @@
# 3 — Hex bolt & nut — real threads
> **Prompt**
>
> Create an M12 hex bolt 45 mm long with a real threaded shaft and a standard hex head, plus its matching hex nut, placed side by side.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="03-hex-bolt-and-nut.gif" alt="Hex bolt & nut — real threads — orbiting render" width="380"></p>
**Parametric controls:** 3 dimensions · 2 colours
**What it demonstrates**
- **Real ISO metric threads** via BOSL2 `screw()` / `nut()` (spec `"M12x1.75"`) — not faked with stacked cylinders
- A complete fastener set: hex bolt + matching hex nut
- Standard spec string drives pitch, head, and thread profile
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `screw_length` | `45` | `[10:1:100]` | Length of the screw shaft |
<details>
<summary>OpenSCAD source — <code>03-hex-bolt-and-nut.scad</code></summary>
```scad
include <BOSL2/std.scad>
include <BOSL2/screws.scad>
/* [Screw Parameters] */
// The screw size and pitch specification
screw_spec = "M12x1.75";
// Length of the screw shaft
screw_length = 45; // [10:1:100]
/* [Colors] */
bolt_color = "LightSlateGray";
nut_color = "LightSlateGray";
$fn = 64;
// Place bolt on the left
translate([-15, 0, 0])
color(bolt_color)
screw(spec=screw_spec, l=screw_length, head="hex");
// Place nut on the right
translate([15, 0, 0])
color(nut_color)
nut(spec=screw_spec);
```
</details>
-25
View File
@@ -1,25 +0,0 @@
include <BOSL2/std.scad>
include <BOSL2/screws.scad>
/* [Screw Parameters] */
// The screw size and pitch specification
screw_spec = "M12x1.75";
// Length of the screw shaft
screw_length = 45; // [10:1:100]
/* [Colors] */
bolt_color = "LightSlateGray";
nut_color = "LightSlateGray";
$fn = 64;
// Place bolt on the left
translate([-15, 0, 0])
color(bolt_color)
screw(spec=screw_spec, l=screw_length, head="hex");
// Place nut on the right
translate([15, 0, 0])
color(nut_color)
nut(spec=screw_spec);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

-182
View File
@@ -1,182 +0,0 @@
# 4 — Honeycomb lightweight bracket
> **Prompt**
>
> Design a 90-degree angle mounting bracket with 80x80 mm flanges that are 5 mm thick, lightened with a hexagonal honeycomb cutout pattern on both faces, four M5 mounting holes, and a filleted inside corner.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="04-honeycomb-bracket.gif" alt="Honeycomb lightweight bracket — orbiting render" width="380"></p>
**Parametric controls:** 13 dimensions · 1 colour
**What it demonstrates**
- Generative hexagonal honeycomb lightening pattern, kept manifold with a solid border frame
- 90° L-bracket with a filleted inner corner for strength
- Four M5 mounting holes
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `flange_length` | `80` | `[40:1:150]` | Length of each flange from the outer corner |
| `bracket_width` | `80` | `[40:1:150]` | Width of the bracket |
| `thickness` | `5` | `[2:1:15]` | Thickness of the flanges |
| `corner_fillet` | `5` | `[1:1:15]` | Radius of the inner corner fillet |
| `hole_dia` | `5.2` | `[2:0.1:10]` | Diameter of the mounting holes (M5 clearance) |
| `hole_boss_dia` | `16` | `[8:1:30]` | Solid boundary diameter around each hole |
| `hole_offset_x` | `45` | `[10:1:100]` | Hole X position (distance from outer corner) |
| `hole_offset_y1` | `20` | `[10:1:100]` | Hole Y1 position (distance from side edge) |
| `hole_offset_y2` | `60` | `[10:1:100]` | Hole Y2 position (distance from side edge) |
| `hex_flat_to_flat` | `12` | `[5:1:30]` | Flat-to-flat inner diameter of each hexagon |
| `hex_wall` | `3` | `[1:0.5:10]` | Wall thickness between hexagons |
| `border_thickness` | `6` | `[2:1:20]` | Solid border thickness around the edges |
| `bracket_color` | `"#4682B4"` | `color` | |
<details>
<summary>OpenSCAD source — <code>04-honeycomb-bracket.scad</code></summary>
```scad
// 90-Degree Mounting Bracket with Honeycomb
/* [Dimensions] */
// Length of each flange from the outer corner
flange_length = 80; // [40:1:150]
// Width of the bracket
bracket_width = 80; // [40:1:150]
// Thickness of the flanges
thickness = 5; // [2:1:15]
// Radius of the inner corner fillet
corner_fillet = 5; // [1:1:15]
/* [Mounting Holes] */
// Diameter of the mounting holes (M5 clearance)
hole_dia = 5.2; // [2:0.1:10]
// Solid boundary diameter around each hole
hole_boss_dia = 16; // [8:1:30]
// Hole X position (distance from outer corner)
hole_offset_x = 45; // [10:1:100]
// Hole Y1 position (distance from side edge)
hole_offset_y1 = 20; // [10:1:100]
// Hole Y2 position (distance from side edge)
hole_offset_y2 = 60; // [10:1:100]
/* [Honeycomb Pattern] */
// Flat-to-flat inner diameter of each hexagon
hex_flat_to_flat = 12; // [5:1:30]
// Wall thickness between hexagons
hex_wall = 3; // [1:0.5:10]
// Solid border thickness around the edges
border_thickness = 6; // [2:1:20]
/* [Rendering] */
bracket_color = "#4682B4"; // color
$fn = 64;
// Combine hole positions into an array (4 holes total, 2 per flange)
hole_positions = [
[hole_offset_x, hole_offset_y1],
[hole_offset_x, hole_offset_y2]
];
// Helper module to generate a 2D grid of hexagons
module hex_grid(w, h, d, wall) {
S = d + wall;
dx = S;
dy = S * sin(60);
cols = ceil(w / dx) + 1;
rows = ceil(h / dy) + 1;
for (i = [-1 : cols]) {
for (j = [-1 : rows]) {
x = i * dx + (j % 2) * (dx / 2);
y = j * dy;
translate([x, y]) rotate(30) circle(d = d / cos(30), $fn=6);
}
}
}
// Generates the 2D masking area to keep borders and bosses solid
module honeycomb_mask() {
difference() {
// Safe inner zone
translate([thickness + corner_fillet + border_thickness, border_thickness])
square([flange_length - (thickness + corner_fillet + border_thickness * 2),
bracket_width - border_thickness * 2]);
// Subtract bosses around the mounting holes
for (pos = hole_positions) {
translate(pos) circle(d = hole_boss_dia);
}
}
}
// The final 2D honeycomb cutout pattern
module honeycomb_cut_2d() {
intersection() {
// Generate full grid slightly larger than needed to ensure coverage
translate([-10, -10]) hex_grid(flange_length + 20, bracket_width + 20, hex_flat_to_flat, hex_wall);
honeycomb_mask();
}
}
// 2D L-profile of the bracket
module bracket_profile() {
R_in = corner_fillet;
R_out = thickness + corner_fillet;
C = [thickness + corner_fillet, thickness + corner_fillet];
union() {
// Flange 1 right of corner
translate([C.x, 0]) square([flange_length - C.x, thickness]);
// Flange 2 above corner
translate([0, C.y]) square([thickness, flange_length - C.y]);
// Filleted Corner section
intersection() {
square([C.x, C.y]);
translate(C) difference() {
circle(r = R_out);
circle(r = R_in);
}
}
}
}
// Build the final 3D part
color(bracket_color)
difference() {
// 1. Base solid bracket (L-extrusion)
translate([0, bracket_width, 0])
rotate([90, 0, 0])
linear_extrude(bracket_width)
bracket_profile();
// 2. Flange 1 holes (Z-axis)
for (pos = hole_positions) {
translate([pos.x, pos.y, -1])
cylinder(d = hole_dia, h = thickness + 2);
}
// 3. Flange 2 holes (X-axis)
for (pos = hole_positions) {
translate([-1, pos.y, pos.x])
rotate([0, 90, 0])
cylinder(d = hole_dia, h = thickness + 2);
}
// 4. Flange 1 honeycomb cutout
translate([0, 0, -1])
linear_extrude(height = thickness + 2)
honeycomb_cut_2d();
// 5. Flange 2 honeycomb cutout
translate([thickness + 1, 0, 0])
rotate([0, -90, 0])
linear_extrude(height = thickness + 2)
honeycomb_cut_2d();
}
```
</details>
-139
View File
@@ -1,139 +0,0 @@
// 90-Degree Mounting Bracket with Honeycomb
/* [Dimensions] */
// Length of each flange from the outer corner
flange_length = 80; // [40:1:150]
// Width of the bracket
bracket_width = 80; // [40:1:150]
// Thickness of the flanges
thickness = 5; // [2:1:15]
// Radius of the inner corner fillet
corner_fillet = 5; // [1:1:15]
/* [Mounting Holes] */
// Diameter of the mounting holes (M5 clearance)
hole_dia = 5.2; // [2:0.1:10]
// Solid boundary diameter around each hole
hole_boss_dia = 16; // [8:1:30]
// Hole X position (distance from outer corner)
hole_offset_x = 45; // [10:1:100]
// Hole Y1 position (distance from side edge)
hole_offset_y1 = 20; // [10:1:100]
// Hole Y2 position (distance from side edge)
hole_offset_y2 = 60; // [10:1:100]
/* [Honeycomb Pattern] */
// Flat-to-flat inner diameter of each hexagon
hex_flat_to_flat = 12; // [5:1:30]
// Wall thickness between hexagons
hex_wall = 3; // [1:0.5:10]
// Solid border thickness around the edges
border_thickness = 6; // [2:1:20]
/* [Rendering] */
bracket_color = "#4682B4"; // color
$fn = 64;
// Combine hole positions into an array (4 holes total, 2 per flange)
hole_positions = [
[hole_offset_x, hole_offset_y1],
[hole_offset_x, hole_offset_y2]
];
// Helper module to generate a 2D grid of hexagons
module hex_grid(w, h, d, wall) {
S = d + wall;
dx = S;
dy = S * sin(60);
cols = ceil(w / dx) + 1;
rows = ceil(h / dy) + 1;
for (i = [-1 : cols]) {
for (j = [-1 : rows]) {
x = i * dx + (j % 2) * (dx / 2);
y = j * dy;
translate([x, y]) rotate(30) circle(d = d / cos(30), $fn=6);
}
}
}
// Generates the 2D masking area to keep borders and bosses solid
module honeycomb_mask() {
difference() {
// Safe inner zone
translate([thickness + corner_fillet + border_thickness, border_thickness])
square([flange_length - (thickness + corner_fillet + border_thickness * 2),
bracket_width - border_thickness * 2]);
// Subtract bosses around the mounting holes
for (pos = hole_positions) {
translate(pos) circle(d = hole_boss_dia);
}
}
}
// The final 2D honeycomb cutout pattern
module honeycomb_cut_2d() {
intersection() {
// Generate full grid slightly larger than needed to ensure coverage
translate([-10, -10]) hex_grid(flange_length + 20, bracket_width + 20, hex_flat_to_flat, hex_wall);
honeycomb_mask();
}
}
// 2D L-profile of the bracket
module bracket_profile() {
R_in = corner_fillet;
R_out = thickness + corner_fillet;
C = [thickness + corner_fillet, thickness + corner_fillet];
union() {
// Flange 1 right of corner
translate([C.x, 0]) square([flange_length - C.x, thickness]);
// Flange 2 above corner
translate([0, C.y]) square([thickness, flange_length - C.y]);
// Filleted Corner section
intersection() {
square([C.x, C.y]);
translate(C) difference() {
circle(r = R_out);
circle(r = R_in);
}
}
}
}
// Build the final 3D part
color(bracket_color)
difference() {
// 1. Base solid bracket (L-extrusion)
translate([0, bracket_width, 0])
rotate([90, 0, 0])
linear_extrude(bracket_width)
bracket_profile();
// 2. Flange 1 holes (Z-axis)
for (pos = hole_positions) {
translate([pos.x, pos.y, -1])
cylinder(d = hole_dia, h = thickness + 2);
}
// 3. Flange 2 holes (X-axis)
for (pos = hole_positions) {
translate([-1, pos.y, pos.x])
rotate([0, 90, 0])
cylinder(d = hole_dia, h = thickness + 2);
}
// 4. Flange 1 honeycomb cutout
translate([0, 0, -1])
linear_extrude(height = thickness + 2)
honeycomb_cut_2d();
// 5. Flange 2 honeycomb cutout
translate([thickness + 1, 0, 0])
rotate([0, -90, 0])
linear_extrude(height = thickness + 2)
honeycomb_cut_2d();
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

-168
View File
@@ -1,168 +0,0 @@
# 5 — NACA 2412 tapered wing
> **Prompt**
>
> Model a tapered aircraft wing section using a real NACA 2412 airfoil: 120 mm root chord tapering to 80 mm tip over a 200 mm span, with two spanwise spar tubes and a few lightening holes.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="05-naca-airfoil-wing.gif" alt="NACA 2412 tapered wing — orbiting render" width="380"></p>
**Parametric controls:** 9 dimensions · 1 colour
**What it demonstrates**
- A true **NACA 4-digit airfoil** built from the thickness + camber equations (cosine-spaced points)
- Tapered loft from a 120 mm root chord to an 80 mm tip over the span
- Spanwise spar bores and a row of lightening holes
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `root_chord` | `120` | `[50:10:300]` | Tapered Wing Section Parameters |
| `tip_chord` | `80` | `[30:10:200]` | |
| `span` | `200` | `[50:10:500]` | |
| `naca_m` | `0.02` | `[0.00:0.01:0.09]` | First digit (max camber in hundredths) |
| `naca_p` | `0.40` | `[0.10:0.10:0.90]` | Second digit (position of max camber in tenths) |
| `naca_t` | `0.12` | `[0.05:0.01:0.30]` | Last two digits (max thickness in hundredths) |
| `spar_radius` | `4` | `[1:0.5:10]` | |
| `lightening_holes` | `5` | `[0:1:10]` | |
| `wing_color` | `"SteelBlue"` | `[SteelBlue, Silver, Orange, White, DimGray]` | |
<details>
<summary>OpenSCAD source — <code>05-naca-airfoil-wing.scad</code></summary>
```scad
// Tapered Wing Section Parameters
/* [Wing Geometry] */
root_chord = 120; // [50:10:300]
tip_chord = 80; // [30:10:200]
span = 200; // [50:10:500]
/* [NACA 4-Digit Profile] */
// First digit (max camber in hundredths)
naca_m = 0.02; // [0.00:0.01:0.09]
// Second digit (position of max camber in tenths)
naca_p = 0.40; // [0.10:0.10:0.90]
// Last two digits (max thickness in hundredths)
naca_t = 0.12; // [0.05:0.01:0.30]
/* [Structural Features] */
spar_radius = 4; // [1:0.5:10]
lightening_holes = 5; // [0:1:10]
/* [Appearance] */
wing_color = "SteelBlue"; // [SteelBlue, Silver, Orange, White, DimGray]
$fn = 64;
// --- Mathematical Functions for NACA 4-Digit Airfoil ---
function naca_camber(x, m, p) =
(m == 0 || p == 0) ? 0 :
(x < p) ? (m / pow(p,2)) * (2 * p * x - pow(x,2))
: (m / pow(1-p,2)) * ((1 - 2*p) + 2 * p * x - pow(x,2));
function naca_camber_deriv(x, m, p) =
(m == 0 || p == 0) ? 0 :
(x < p) ? (2 * m / pow(p,2)) * (p - x)
: (2 * m / pow(1-p,2)) * (p - x);
function naca_points(m, p, t, N=80) =
let (
upper = [for (i=[0:N])
let (
x = 0.5 * (1 - cos(i * 180 / N)),
yt = 5 * t * (0.2969 * sqrt(max(0, x)) - 0.1260 * x - 0.3516 * pow(x,2) + 0.2843 * pow(x,3) - 0.1015 * pow(x,4)),
yc = naca_camber(x, m, p),
dyc = naca_camber_deriv(x, m, p),
theta = atan(dyc)
)
[x - yt * sin(theta), yc + yt * cos(theta)]
],
lower = [for (i=[N-1:-1:1]) // Skip duplicate endpoints to form a closed loop
let (
x = 0.5 * (1 - cos(i * 180 / N)),
yt = 5 * t * (0.2969 * sqrt(max(0, x)) - 0.1260 * x - 0.3516 * pow(x,2) + 0.2843 * pow(x,3) - 0.1015 * pow(x,4)),
yc = naca_camber(x, m, p),
dyc = naca_camber_deriv(x, m, p),
theta = atan(dyc)
)
[x + yt * sin(theta), yc - yt * cos(theta)]
]
)
concat(upper, lower);
// --- Modules ---
// Generates a rounded-end cylinder between two points, safely extended to cut cleanly
module strut(p1, p2, r) {
v = p2 - p1;
ext = 5;
dir = v / norm(v);
p1_ext = p1 - dir * ext;
p2_ext = p2 + dir * ext;
hull() {
translate(p1_ext) sphere(r=r);
translate(p2_ext) sphere(r=r);
}
}
// --- Main Geometry Assembly ---
// Calculate standard spar locations (25% and 70% of chord length)
x_spar1 = 0.25;
x_spar2 = 0.70;
// Calculate 3D coordinates for the spar tubes to correctly follow the camber and taper
p1_root = [x_spar1 * root_chord, naca_camber(x_spar1, naca_m, naca_p) * root_chord, 0];
p1_tip = [x_spar1 * tip_chord, naca_camber(x_spar1, naca_m, naca_p) * tip_chord, span];
p2_root = [x_spar2 * root_chord, naca_camber(x_spar2, naca_m, naca_p) * root_chord, 0];
p2_tip = [x_spar2 * tip_chord, naca_camber(x_spar2, naca_m, naca_p) * tip_chord, span];
// Rotate wing to lay "flat" for standard viewing
// (Span extends along +Y, chord along +X, thickness aligns with Z)
color(wing_color)
rotate([-90, 0, 0])
difference() {
// Solid Wing Shape
linear_extrude(height = span, scale = tip_chord / root_chord)
scale([root_chord, root_chord])
polygon(naca_points(naca_m, naca_p, naca_t));
// Spanwise Spar Tube Cuts
strut(p1_root, p1_tip, spar_radius);
strut(p2_root, p2_tip, spar_radius);
// Lightening Hole Cuts (spaced evenly along span)
if (lightening_holes > 0) {
for (i = [1 : lightening_holes]) {
let (
// Space holes along the span
z_pos = i * span / (lightening_holes + 1),
// Determine the chord length at this spanwise position
local_chord = root_chord + (tip_chord - root_chord) * (z_pos / span),
// Position halfway between the two spars
x_pos = 0.475 * local_chord,
// Make the hole size proportional to the local chord length
hole_r = local_chord * 0.12
)
// Cut through the airfoil thickness (Y axis in the unrotated frame)
translate([x_pos, 0, z_pos])
rotate([90, 0, 0])
cylinder(h=root_chord * 2, r=hole_r, center=true);
}
}
}
```
</details>
-129
View File
@@ -1,129 +0,0 @@
// Tapered Wing Section Parameters
/* [Wing Geometry] */
root_chord = 120; // [50:10:300]
tip_chord = 80; // [30:10:200]
span = 200; // [50:10:500]
/* [NACA 4-Digit Profile] */
// First digit (max camber in hundredths)
naca_m = 0.02; // [0.00:0.01:0.09]
// Second digit (position of max camber in tenths)
naca_p = 0.40; // [0.10:0.10:0.90]
// Last two digits (max thickness in hundredths)
naca_t = 0.12; // [0.05:0.01:0.30]
/* [Structural Features] */
spar_radius = 4; // [1:0.5:10]
lightening_holes = 5; // [0:1:10]
/* [Appearance] */
wing_color = "SteelBlue"; // [SteelBlue, Silver, Orange, White, DimGray]
$fn = 64;
// --- Mathematical Functions for NACA 4-Digit Airfoil ---
function naca_camber(x, m, p) =
(m == 0 || p == 0) ? 0 :
(x < p) ? (m / pow(p,2)) * (2 * p * x - pow(x,2))
: (m / pow(1-p,2)) * ((1 - 2*p) + 2 * p * x - pow(x,2));
function naca_camber_deriv(x, m, p) =
(m == 0 || p == 0) ? 0 :
(x < p) ? (2 * m / pow(p,2)) * (p - x)
: (2 * m / pow(1-p,2)) * (p - x);
function naca_points(m, p, t, N=80) =
let (
upper = [for (i=[0:N])
let (
x = 0.5 * (1 - cos(i * 180 / N)),
yt = 5 * t * (0.2969 * sqrt(max(0, x)) - 0.1260 * x - 0.3516 * pow(x,2) + 0.2843 * pow(x,3) - 0.1015 * pow(x,4)),
yc = naca_camber(x, m, p),
dyc = naca_camber_deriv(x, m, p),
theta = atan(dyc)
)
[x - yt * sin(theta), yc + yt * cos(theta)]
],
lower = [for (i=[N-1:-1:1]) // Skip duplicate endpoints to form a closed loop
let (
x = 0.5 * (1 - cos(i * 180 / N)),
yt = 5 * t * (0.2969 * sqrt(max(0, x)) - 0.1260 * x - 0.3516 * pow(x,2) + 0.2843 * pow(x,3) - 0.1015 * pow(x,4)),
yc = naca_camber(x, m, p),
dyc = naca_camber_deriv(x, m, p),
theta = atan(dyc)
)
[x + yt * sin(theta), yc - yt * cos(theta)]
]
)
concat(upper, lower);
// --- Modules ---
// Generates a rounded-end cylinder between two points, safely extended to cut cleanly
module strut(p1, p2, r) {
v = p2 - p1;
ext = 5;
dir = v / norm(v);
p1_ext = p1 - dir * ext;
p2_ext = p2 + dir * ext;
hull() {
translate(p1_ext) sphere(r=r);
translate(p2_ext) sphere(r=r);
}
}
// --- Main Geometry Assembly ---
// Calculate standard spar locations (25% and 70% of chord length)
x_spar1 = 0.25;
x_spar2 = 0.70;
// Calculate 3D coordinates for the spar tubes to correctly follow the camber and taper
p1_root = [x_spar1 * root_chord, naca_camber(x_spar1, naca_m, naca_p) * root_chord, 0];
p1_tip = [x_spar1 * tip_chord, naca_camber(x_spar1, naca_m, naca_p) * tip_chord, span];
p2_root = [x_spar2 * root_chord, naca_camber(x_spar2, naca_m, naca_p) * root_chord, 0];
p2_tip = [x_spar2 * tip_chord, naca_camber(x_spar2, naca_m, naca_p) * tip_chord, span];
// Rotate wing to lay "flat" for standard viewing
// (Span extends along +Y, chord along +X, thickness aligns with Z)
color(wing_color)
rotate([-90, 0, 0])
difference() {
// Solid Wing Shape
linear_extrude(height = span, scale = tip_chord / root_chord)
scale([root_chord, root_chord])
polygon(naca_points(naca_m, naca_p, naca_t));
// Spanwise Spar Tube Cuts
strut(p1_root, p1_tip, spar_radius);
strut(p2_root, p2_tip, spar_radius);
// Lightening Hole Cuts (spaced evenly along span)
if (lightening_holes > 0) {
for (i = [1 : lightening_holes]) {
let (
// Space holes along the span
z_pos = i * span / (lightening_holes + 1),
// Determine the chord length at this spanwise position
local_chord = root_chord + (tip_chord - root_chord) * (z_pos / span),
// Position halfway between the two spars
x_pos = 0.475 * local_chord,
// Make the hole size proportional to the local chord length
hole_r = local_chord * 0.12
)
// Cut through the airfoil thickness (Y axis in the unrotated frame)
translate([x_pos, 0, z_pos])
rotate([90, 0, 0])
cylinder(h=root_chord * 2, r=hole_r, center=true);
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

-115
View File
@@ -1,115 +0,0 @@
# 6 — Threaded jar & screw-on lid
> **Prompt**
>
> Create a small storage jar with external screw threads at the neck and a matching screw-on lid with internal threads. Jar body 60 mm diameter, 70 mm tall, 2.5 mm walls; show the lid unscrewed and sitting beside the jar.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="06-threaded-jar-and-lid.gif" alt="Threaded jar & screw-on lid — orbiting render" width="380"></p>
**Parametric controls:** 9 dimensions · 2 colours
**What it demonstrates**
- Two **mating** threaded parts that share a pitch and diameter (external neck thread + internal lid thread) via BOSL2 threading
- Hollow jar body with printable walls
- Lid with a fluted grip rim, shown unscrewed beside the jar
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `jar_diameter` | `60` | `[20:1:100]` | |
| `jar_height` | `70` | `[20:1:200]` | |
| `wall_thickness` | `2.5` | `[1:0.1:5]` | |
| `neck_diameter` | `55` | `[20:1:100]` | |
| `neck_height` | `15` | `[5:1:30]` | |
| `thread_pitch` | `3` | `[1:0.5:5]` | |
| `lid_clearance` | `0.5` | `[0:0.1:2]` | |
<details>
<summary>OpenSCAD source — <code>06-threaded-jar-and-lid.scad</code></summary>
```scad
include <BOSL2/std.scad>
include <BOSL2/threading.scad>
/* [Jar Dimensions] */
jar_diameter = 60; // [20:1:100]
jar_height = 70; // [20:1:200]
wall_thickness = 2.5; // [1:0.1:5]
/* [Neck and Threading] */
neck_diameter = 55; // [20:1:100]
neck_height = 15; // [5:1:30]
thread_pitch = 3; // [1:0.5:5]
lid_clearance = 0.5; // [0:0.1:2]
/* [Colors] */
jar_color = "LightSkyBlue";
lid_color = "SteelBlue";
$fn = 64;
shoulder_height = 5;
body_height = jar_height - neck_height - shoulder_height;
// Position jar
translate([-35, 0, 0])
color(jar_color)
difference() {
// Solid exterior
union() {
// Body
cylinder(d=jar_diameter, h=body_height);
// Shoulder
translate([0, 0, body_height])
cylinder(d1=jar_diameter, d2=neck_diameter, h=shoulder_height);
// Neck
translate([0, 0, body_height + shoulder_height])
threaded_rod(d=neck_diameter, l=neck_height, pitch=thread_pitch, anchor=BOTTOM);
}
// Hollow interior
union() {
// Body interior
translate([0, 0, wall_thickness])
cylinder(d=jar_diameter - 2 * wall_thickness, h=body_height - wall_thickness + 0.01);
// Shoulder interior
translate([0, 0, body_height])
cylinder(d1=jar_diameter - 2 * wall_thickness, d2=neck_diameter - 2 * wall_thickness, h=shoulder_height + 0.01);
// Neck interior
translate([0, 0, body_height + shoulder_height])
cylinder(d=neck_diameter - 2 * wall_thickness, h=neck_height + 1);
}
}
// Position lid beside the jar, upside down to show internal threads
translate([35, 0, neck_height + wall_thickness])
rotate([180, 0, 0])
color(lid_color)
difference() {
lid_outer_d = neck_diameter + 2 * wall_thickness + 2 * lid_clearance + 2;
// Lid exterior with grips
difference() {
cylinder(d=lid_outer_d, h=neck_height + wall_thickness);
// Grips
for (i = [0 : 15 : 359]) {
rotate([0, 0, i])
translate([lid_outer_d/2, 0, -1])
cylinder(r=1.5, h=neck_height + wall_thickness + 2, $fn=16);
}
}
// Internal threads cutout
translate([0, 0, -0.1])
threaded_rod(d=neck_diameter + lid_clearance, l=neck_height + 0.2, pitch=thread_pitch, anchor=BOTTOM, internal=true);
}
```
</details>
@@ -1,78 +0,0 @@
include <BOSL2/std.scad>
include <BOSL2/threading.scad>
/* [Jar Dimensions] */
jar_diameter = 60; // [20:1:100]
jar_height = 70; // [20:1:200]
wall_thickness = 2.5; // [1:0.1:5]
/* [Neck and Threading] */
neck_diameter = 55; // [20:1:100]
neck_height = 15; // [5:1:30]
thread_pitch = 3; // [1:0.5:5]
lid_clearance = 0.5; // [0:0.1:2]
/* [Colors] */
jar_color = "LightSkyBlue";
lid_color = "SteelBlue";
$fn = 64;
shoulder_height = 5;
body_height = jar_height - neck_height - shoulder_height;
// Position jar
translate([-35, 0, 0])
color(jar_color)
difference() {
// Solid exterior
union() {
// Body
cylinder(d=jar_diameter, h=body_height);
// Shoulder
translate([0, 0, body_height])
cylinder(d1=jar_diameter, d2=neck_diameter, h=shoulder_height);
// Neck
translate([0, 0, body_height + shoulder_height])
threaded_rod(d=neck_diameter, l=neck_height, pitch=thread_pitch, anchor=BOTTOM);
}
// Hollow interior
union() {
// Body interior
translate([0, 0, wall_thickness])
cylinder(d=jar_diameter - 2 * wall_thickness, h=body_height - wall_thickness + 0.01);
// Shoulder interior
translate([0, 0, body_height])
cylinder(d1=jar_diameter - 2 * wall_thickness, d2=neck_diameter - 2 * wall_thickness, h=shoulder_height + 0.01);
// Neck interior
translate([0, 0, body_height + shoulder_height])
cylinder(d=neck_diameter - 2 * wall_thickness, h=neck_height + 1);
}
}
// Position lid beside the jar, upside down to show internal threads
translate([35, 0, neck_height + wall_thickness])
rotate([180, 0, 0])
color(lid_color)
difference() {
lid_outer_d = neck_diameter + 2 * wall_thickness + 2 * lid_clearance + 2;
// Lid exterior with grips
difference() {
cylinder(d=lid_outer_d, h=neck_height + wall_thickness);
// Grips
for (i = [0 : 15 : 359]) {
rotate([0, 0, i])
translate([lid_outer_d/2, 0, -1])
cylinder(r=1.5, h=neck_height + wall_thickness + 2, $fn=16);
}
}
// Internal threads cutout
translate([0, 0, -0.1])
threaded_rod(d=neck_diameter + lid_clearance, l=neck_height + 0.2, pitch=thread_pitch, anchor=BOTTOM, internal=true);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

-98
View File
@@ -1,98 +0,0 @@
# 7 — Right-angle bevel gear drive
> **Prompt**
>
> Build a right-angle bevel gear drive: a 24-tooth bevel gear on a vertical shaft meshing at 90 degrees with a 16-tooth bevel pinion on a horizontal shaft, each on a short stub shaft.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="07-bevel-gear-drive.gif" alt="Right-angle bevel gear drive — orbiting render" width="380"></p>
**Parametric controls:** 9 dimensions · 3 colours
**What it demonstrates**
- A meshing **bevel gear pair** at 90° via BOSL2 `bevel_gear()` with matched pitch cones
- 24-tooth gear + 16-tooth pinion, each on a stub shaft
- Three colours for gear / pinion / shafts
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `gear1_teeth` | `24` | `[10:1:60]` | |
| `gear2_teeth` | `16` | `[10:1:60]` | |
| `circular_pitch` | `5` | `[2:0.5:10]` | |
| `face_width` | `8` | `[4:1:20]` | |
| `bore_diameter` | `5` | `[2:0.5:20]` | |
| `shaft_length` | `30` | `[10:1:50]` | |
| `shaft_diameter` | `5` | `[2:0.5:20]` | |
| `gear2_spin` | `11.25` | `[0:0.1:45]` | |
<details>
<summary>OpenSCAD source — <code>07-bevel-gear-drive.scad</code></summary>
```scad
include <BOSL2/std.scad>
include <BOSL2/gears.scad>
/* [Gear Specifications] */
gear1_teeth = 24; // [10:1:60]
gear2_teeth = 16; // [10:1:60]
circular_pitch = 5; // [2:0.5:10]
face_width = 8; // [4:1:20]
bore_diameter = 5; // [2:0.5:20]
shaft_length = 30; // [10:1:50]
shaft_diameter = 5; // [2:0.5:20]
gear2_spin = 11.25; // [0:0.1:45]
/* [Colors] */
gear1_color = "SteelBlue";
gear2_color = "DarkOrange";
shaft_color = "Silver";
$fn = 64;
module gear_system() {
// Gear 1: Vertical (Z-axis)
color(gear1_color)
bevel_gear(
circ_pitch = circular_pitch,
teeth = gear1_teeth,
mate_teeth = gear2_teeth,
face_width = face_width,
bore = bore_diameter,
spiral = 0,
anchor = "apex"
);
// Vertical Shaft
color(shaft_color)
translate([0, 0, -shaft_length/2])
cylinder(h=shaft_length, d=shaft_diameter, center=true);
// Gear 2: Horizontal (X-axis)
color(gear2_color)
rotate([0, 90, 0])
rotate([0, 0, gear2_spin]) // Fine-tune tooth meshing
bevel_gear(
circ_pitch = circular_pitch,
teeth = gear2_teeth,
mate_teeth = gear1_teeth,
face_width = face_width,
bore = bore_diameter,
spiral = 0,
anchor = "apex"
);
// Horizontal Shaft
color(shaft_color)
rotate([0, 90, 0])
translate([0, 0, -shaft_length/2])
cylinder(h=shaft_length, d=shaft_diameter, center=true);
}
gear_system();
```
</details>
-60
View File
@@ -1,60 +0,0 @@
include <BOSL2/std.scad>
include <BOSL2/gears.scad>
/* [Gear Specifications] */
gear1_teeth = 24; // [10:1:60]
gear2_teeth = 16; // [10:1:60]
circular_pitch = 5; // [2:0.5:10]
face_width = 8; // [4:1:20]
bore_diameter = 5; // [2:0.5:20]
shaft_length = 30; // [10:1:50]
shaft_diameter = 5; // [2:0.5:20]
gear2_spin = 11.25; // [0:0.1:45]
/* [Colors] */
gear1_color = "SteelBlue";
gear2_color = "DarkOrange";
shaft_color = "Silver";
$fn = 64;
module gear_system() {
// Gear 1: Vertical (Z-axis)
color(gear1_color)
bevel_gear(
circ_pitch = circular_pitch,
teeth = gear1_teeth,
mate_teeth = gear2_teeth,
face_width = face_width,
bore = bore_diameter,
spiral = 0,
anchor = "apex"
);
// Vertical Shaft
color(shaft_color)
translate([0, 0, -shaft_length/2])
cylinder(h=shaft_length, d=shaft_diameter, center=true);
// Gear 2: Horizontal (X-axis)
color(gear2_color)
rotate([0, 90, 0])
rotate([0, 0, gear2_spin]) // Fine-tune tooth meshing
bevel_gear(
circ_pitch = circular_pitch,
teeth = gear2_teeth,
mate_teeth = gear1_teeth,
face_width = face_width,
bore = bore_diameter,
spiral = 0,
anchor = "apex"
);
// Horizontal Shaft
color(shaft_color)
rotate([0, 90, 0])
translate([0, 0, -shaft_length/2])
cylinder(h=shaft_length, d=shaft_diameter, center=true);
}
gear_system();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

-123
View File
@@ -1,123 +0,0 @@
# 8 — Centrifugal pump impeller
> **Prompt**
>
> Design a centrifugal pump impeller: a 90 mm diameter back-plate with a central 12 mm bore and a raised hub, and seven backward-curved blades that sweep from the hub out to the rim, each blade curving smoothly along its path.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="08-centrifugal-impeller.gif" alt="Centrifugal pump impeller — orbiting render" width="380"></p>
**Parametric controls:** 10 dimensions · 1 colour
**What it demonstrates**
- Seven **backward-curved blades** swept along curved paths (BOSL2) in a clean radial array
- Raised central hub with a 12 mm bore on a circular back-plate
- Smooth turbomachinery surfaces, fused manifold to the plate
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `impeller_diameter` | `90` | `[50:1:150]` | Outer diameter of the impeller back-plate |
| `bore_diameter` | `12` | `[4:0.5:30]` | Diameter of the central bore hole |
| `backplate_thickness` | `3` | `[1:0.5:10]` | Thickness of the main back-plate |
| `hub_diameter` | `26` | `[15:1:50]` | Outer diameter of the raised central hub |
| `hub_height` | `20` | `[10:1:60]` | Total height of the hub from the bottom of the back-plate |
| `blade_count` | `7` | `[3:1:15]` | Number of backward-curved blades |
| `blade_height` | `12` | `[2:1:40]` | Height of the blades above the back-plate |
| `blade_thickness` | `3` | `[1:0.5:8]` | Thickness of each blade |
| `sweep_angle` | `75` | `[10:1:180]` | The angle (in degrees) that the blades sweep backwards |
| `part_color` | `"#B0C4DE"` | `LightSteelBlue` | |
| `steps` | `48` | `Curve resolution` | |
<details>
<summary>OpenSCAD source — <code>08-centrifugal-impeller.scad</code></summary>
```scad
include <BOSL2/std.scad>
/* [Main Dimensions] */
// Outer diameter of the impeller back-plate
impeller_diameter = 90; // [50:1:150]
// Diameter of the central bore hole
bore_diameter = 12; // [4:0.5:30]
// Thickness of the main back-plate
backplate_thickness = 3; // [1:0.5:10]
/* [Hub Dimensions] */
// Outer diameter of the raised central hub
hub_diameter = 26; // [15:1:50]
// Total height of the hub from the bottom of the back-plate
hub_height = 20; // [10:1:60]
/* [Blade Parameters] */
// Number of backward-curved blades
blade_count = 7; // [3:1:15]
// Height of the blades above the back-plate
blade_height = 12; // [2:1:40]
// Thickness of each blade
blade_thickness = 3; // [1:0.5:8]
// The angle (in degrees) that the blades sweep backwards
sweep_angle = 75; // [10:1:180]
/* [Appearance] */
part_color = "#B0C4DE"; // LightSteelBlue
$fn = 64;
module centrifugal_impeller() {
difference() {
union() {
// Back-plate
cyl(d=impeller_diameter, l=backplate_thickness, chamfer1=1, anchor=BOTTOM);
// Raised Hub
cyl(d=hub_diameter, l=hub_height, chamfer2=1.5, anchor=BOTTOM);
// Blades (logarithmic spiral for constant incidence angle)
// Inner radius overlaps the hub slightly to ensure a solid union
blade_inner_r = (hub_diameter / 2) - 0.5;
// Outer radius stops slightly short of the rim so the round endcaps don't overhang
blade_outer_r = (impeller_diameter / 2) - (blade_thickness / 2);
steps = 48; // Curve resolution
// Calculate the 2D path of the sweeping blade
pts = [
for(i = [0 : steps])
let(
t = i / steps,
r = blade_inner_r * pow(blade_outer_r / blade_inner_r, t),
a = -sweep_angle * t
)
[r * cos(a), r * sin(a)]
];
// Distribute and extrude the 7 blades
for(i = [0 : blade_count - 1]) {
zrot(i * 360 / blade_count)
up(backplate_thickness)
linear_extrude(height = blade_height)
stroke(pts, width=blade_thickness, endcaps="round");
}
}
// Central Bore (subtracted at the end to ensure a clean hole through hub and blades)
down(1)
cyl(d=bore_diameter, l=hub_height + 2, anchor=BOTTOM);
}
}
color(part_color)
centrifugal_impeller();
```
</details>
@@ -1,82 +0,0 @@
include <BOSL2/std.scad>
/* [Main Dimensions] */
// Outer diameter of the impeller back-plate
impeller_diameter = 90; // [50:1:150]
// Diameter of the central bore hole
bore_diameter = 12; // [4:0.5:30]
// Thickness of the main back-plate
backplate_thickness = 3; // [1:0.5:10]
/* [Hub Dimensions] */
// Outer diameter of the raised central hub
hub_diameter = 26; // [15:1:50]
// Total height of the hub from the bottom of the back-plate
hub_height = 20; // [10:1:60]
/* [Blade Parameters] */
// Number of backward-curved blades
blade_count = 7; // [3:1:15]
// Height of the blades above the back-plate
blade_height = 12; // [2:1:40]
// Thickness of each blade
blade_thickness = 3; // [1:0.5:8]
// The angle (in degrees) that the blades sweep backwards
sweep_angle = 75; // [10:1:180]
/* [Appearance] */
part_color = "#B0C4DE"; // LightSteelBlue
$fn = 64;
module centrifugal_impeller() {
difference() {
union() {
// Back-plate
cyl(d=impeller_diameter, l=backplate_thickness, chamfer1=1, anchor=BOTTOM);
// Raised Hub
cyl(d=hub_diameter, l=hub_height, chamfer2=1.5, anchor=BOTTOM);
// Blades (logarithmic spiral for constant incidence angle)
// Inner radius overlaps the hub slightly to ensure a solid union
blade_inner_r = (hub_diameter / 2) - 0.5;
// Outer radius stops slightly short of the rim so the round endcaps don't overhang
blade_outer_r = (impeller_diameter / 2) - (blade_thickness / 2);
steps = 48; // Curve resolution
// Calculate the 2D path of the sweeping blade
pts = [
for(i = [0 : steps])
let(
t = i / steps,
r = blade_inner_r * pow(blade_outer_r / blade_inner_r, t),
a = -sweep_angle * t
)
[r * cos(a), r * sin(a)]
];
// Distribute and extrude the 7 blades
for(i = [0 : blade_count - 1]) {
zrot(i * 360 / blade_count)
up(backplate_thickness)
linear_extrude(height = blade_height)
stroke(pts, width=blade_thickness, endcaps="round");
}
}
// Central Bore (subtracted at the end to ensure a clean hole through hub and blades)
down(1)
cyl(d=bore_diameter, l=hub_height + 2, anchor=BOTTOM);
}
}
color(part_color)
centrifugal_impeller();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

@@ -1,190 +0,0 @@
# 9 — Herringbone planetary gear stage
> **Prompt**
>
> Model a herringbone planetary gear stage at module 1.5: a central sun gear with 18 teeth, three planet gears with 18 teeth each meshing around it, an internal ring gear with 54 teeth, and a carrier plate linking the three planet axles. Color the sun, planets, ring, and carrier differently.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="09-herringbone-planetary-gearbox.gif" alt="Herringbone planetary gear stage — orbiting render" width="380"></p>
**Parametric controls:** 10 dimensions · 4 colours
**What it demonstrates**
- A full **epicyclic assembly**: sun gear + three planets + an internal ring gear + a carrier plate
- **Herringbone** teeth on every gear; ring teeth = sun + 2 × planet (54)
- Four distinct colours for sun / planets / ring / carrier
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `gear_module` | `1.5` | `[1:0.1:3]` | |
| `gear_thickness` | `10` | `[5:1:20]` | |
| `sun_twist` | `10` | `[0:1:30]` | |
| `sun_bore` | `6` | `[3:0.5:10]` | |
| `planet_bore` | `6` | `[3:0.5:10]` | |
<details>
<summary>OpenSCAD source — <code>09-herringbone-planetary-gearbox.scad</code></summary>
```scad
// Title: Herringbone Planetary Gear Stage
// Description: A robust, manifold module 1.5 planetary gear stage with herringbone teeth, carrier plate, and custom colors.
// Version: v4
use <MCAD/involute_gears.scad>
PI_VAL = 3.141592653589793;
/* [Gear Parameters] */
gear_module = 1.5; // [1:0.1:3]
gear_thickness = 10; // [5:1:20]
sun_twist = 10; // [0:1:30]
sun_bore = 6; // [3:0.5:10]
planet_bore = 6; // [3:0.5:10]
/* [Colors] */
sun_color = "Gold";
planet_color = "Tomato";
ring_color = "SteelBlue";
carrier_color = "Silver";
/* [Calculated Constants] */
sun_teeth = 18;
planet_teeth = 18;
ring_teeth = 54;
pitch_sun = gear_module * sun_teeth / 2;
pitch_planet = gear_module * planet_teeth / 2;
pitch_ring = gear_module * ring_teeth / 2;
center_distance = pitch_sun + pitch_planet;
$fn = 64;
// ==============================
// Modules
// ==============================
module single_gear(teeth, mod, half_t, t_angle) {
gear(
number_of_teeth = teeth,
circular_pitch = PI_VAL * mod,
pressure_angle = 20,
clearance = 0.25,
gear_thickness = half_t,
rim_thickness = half_t,
hub_thickness = half_t,
bore_diameter = 0,
twist = t_angle
);
}
module herringbone_gear_base(mod, teeth, thickness, base_twist, is_planet) {
half_t = thickness / 2;
// Maintain constant helix angle across different gear sizes
t_angle = (base_twist * 18 / teeth) * (is_planet ? -1 : 1);
union() {
single_gear(teeth, mod, half_t, t_angle);
translate([0, 0, thickness])
mirror([0, 0, 1])
single_gear(teeth, mod, half_t, t_angle);
}
}
module hb_gear(teeth, is_planet=false, bore=0) {
difference() {
herringbone_gear_base(gear_module, teeth, gear_thickness, sun_twist, is_planet);
if (bore > 0) {
translate([0, 0, -1])
cylinder(r=bore/2, h=gear_thickness+2, $fn=32);
}
}
}
module ring_gear() {
outer_radius = pitch_ring + 4 * gear_module;
// Shift slightly to prevent Z-fighting at top and bottom
translate([0, 0, 0.01])
difference() {
union() {
cylinder(r=outer_radius, h=gear_thickness - 0.02, $fn=128);
// Outer grip ridges
for(i=[0:10:359]) {
rotate([0, 0, i])
translate([outer_radius, 0, 0])
cylinder(r=gear_module, h=gear_thickness - 0.02, $fn=16);
}
}
// Subtract a slightly scaled herringbone gear to form the internal teeth with clearance
translate([0, 0, -0.01])
scale([1.015, 1.015, 1])
rotate([0, 0, 360 / ring_teeth / 2]) // half tooth phase shift
herringbone_gear_base(gear_module, ring_teeth, gear_thickness, sun_twist, is_planet=true);
}
}
module carrier() {
// Carrier plate positioned slightly above the gears
translate([0, 0, gear_thickness + 0.5])
difference() {
union() {
// Main carrier disc
cylinder(r=center_distance + 8, h=3, $fn=64);
// Pins connecting the planet gears
for(i=[0:120:359]) {
rotate([0, 0, i])
translate([center_distance, 0, -gear_thickness - 0.1])
cylinder(r=planet_bore/2 - 0.2, h=gear_thickness + 1.1, $fn=32);
}
// Central pin acting as sun bearing
translate([0, 0, -gear_thickness - 0.1])
cylinder(r=sun_bore/2 - 0.2, h=gear_thickness + 1.1, $fn=32);
}
// Aesthetic weight-saving cutouts
for(i=[0:60:359]) {
rotate([0, 0, i + 30])
translate([center_distance * 0.55, 0, -1])
cylinder(r=6, h=5, $fn=32);
}
// Central hole through the carrier shaft
translate([0, 0, -gear_thickness - 0.2])
cylinder(r=sun_bore/2 - 1.5, h=gear_thickness + 5, $fn=16);
}
}
// ==============================
// Assembly
// ==============================
// Central Sun Gear
color(sun_color)
hb_gear(sun_teeth, is_planet=false, bore=sun_bore);
// Planet Gears (x3)
color(planet_color)
for(i=[0:120:359]) {
rotate([0, 0, i])
translate([center_distance, 0, 0])
rotate([0, 0, 360 / planet_teeth / 2]) // Phase shift by half tooth for perfect meshing
hb_gear(planet_teeth, is_planet=true, bore=planet_bore);
}
// Internal Ring Gear
color(ring_color)
ring_gear();
// Carrier Plate
color(carrier_color)
carrier();
```
</details>
@@ -1,155 +0,0 @@
// Title: Herringbone Planetary Gear Stage
// Description: A robust, manifold module 1.5 planetary gear stage with herringbone teeth, carrier plate, and custom colors.
// Version: v4
use <MCAD/involute_gears.scad>
PI_VAL = 3.141592653589793;
/* [Gear Parameters] */
gear_module = 1.5; // [1:0.1:3]
gear_thickness = 10; // [5:1:20]
sun_twist = 10; // [0:1:30]
sun_bore = 6; // [3:0.5:10]
planet_bore = 6; // [3:0.5:10]
/* [Colors] */
sun_color = "Gold";
planet_color = "Tomato";
ring_color = "SteelBlue";
carrier_color = "Silver";
/* [Calculated Constants] */
sun_teeth = 18;
planet_teeth = 18;
ring_teeth = 54;
pitch_sun = gear_module * sun_teeth / 2;
pitch_planet = gear_module * planet_teeth / 2;
pitch_ring = gear_module * ring_teeth / 2;
center_distance = pitch_sun + pitch_planet;
$fn = 64;
// ==============================
// Modules
// ==============================
module single_gear(teeth, mod, half_t, t_angle) {
gear(
number_of_teeth = teeth,
circular_pitch = PI_VAL * mod,
pressure_angle = 20,
clearance = 0.25,
gear_thickness = half_t,
rim_thickness = half_t,
hub_thickness = half_t,
bore_diameter = 0,
twist = t_angle
);
}
module herringbone_gear_base(mod, teeth, thickness, base_twist, is_planet) {
half_t = thickness / 2;
// Maintain constant helix angle across different gear sizes
t_angle = (base_twist * 18 / teeth) * (is_planet ? -1 : 1);
union() {
single_gear(teeth, mod, half_t, t_angle);
translate([0, 0, thickness])
mirror([0, 0, 1])
single_gear(teeth, mod, half_t, t_angle);
}
}
module hb_gear(teeth, is_planet=false, bore=0) {
difference() {
herringbone_gear_base(gear_module, teeth, gear_thickness, sun_twist, is_planet);
if (bore > 0) {
translate([0, 0, -1])
cylinder(r=bore/2, h=gear_thickness+2, $fn=32);
}
}
}
module ring_gear() {
outer_radius = pitch_ring + 4 * gear_module;
// Shift slightly to prevent Z-fighting at top and bottom
translate([0, 0, 0.01])
difference() {
union() {
cylinder(r=outer_radius, h=gear_thickness - 0.02, $fn=128);
// Outer grip ridges
for(i=[0:10:359]) {
rotate([0, 0, i])
translate([outer_radius, 0, 0])
cylinder(r=gear_module, h=gear_thickness - 0.02, $fn=16);
}
}
// Subtract a slightly scaled herringbone gear to form the internal teeth with clearance
translate([0, 0, -0.01])
scale([1.015, 1.015, 1])
rotate([0, 0, 360 / ring_teeth / 2]) // half tooth phase shift
herringbone_gear_base(gear_module, ring_teeth, gear_thickness, sun_twist, is_planet=true);
}
}
module carrier() {
// Carrier plate positioned slightly above the gears
translate([0, 0, gear_thickness + 0.5])
difference() {
union() {
// Main carrier disc
cylinder(r=center_distance + 8, h=3, $fn=64);
// Pins connecting the planet gears
for(i=[0:120:359]) {
rotate([0, 0, i])
translate([center_distance, 0, -gear_thickness - 0.1])
cylinder(r=planet_bore/2 - 0.2, h=gear_thickness + 1.1, $fn=32);
}
// Central pin acting as sun bearing
translate([0, 0, -gear_thickness - 0.1])
cylinder(r=sun_bore/2 - 0.2, h=gear_thickness + 1.1, $fn=32);
}
// Aesthetic weight-saving cutouts
for(i=[0:60:359]) {
rotate([0, 0, i + 30])
translate([center_distance * 0.55, 0, -1])
cylinder(r=6, h=5, $fn=32);
}
// Central hole through the carrier shaft
translate([0, 0, -gear_thickness - 0.2])
cylinder(r=sun_bore/2 - 1.5, h=gear_thickness + 5, $fn=16);
}
}
// ==============================
// Assembly
// ==============================
// Central Sun Gear
color(sun_color)
hb_gear(sun_teeth, is_planet=false, bore=sun_bore);
// Planet Gears (x3)
color(planet_color)
for(i=[0:120:359]) {
rotate([0, 0, i])
translate([center_distance, 0, 0])
rotate([0, 0, 360 / planet_teeth / 2]) // Phase shift by half tooth for perfect meshing
hb_gear(planet_teeth, is_planet=true, bore=planet_bore);
}
// Internal Ring Gear
color(ring_color)
ring_gear();
// Carrier Plate
color(carrier_color)
carrier();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

@@ -1,229 +0,0 @@
# 10 — 9-cylinder radial aircraft engine
> **Prompt**
>
> Design a 9-cylinder radial aircraft engine: a central round crankcase with nine finned cylinders arranged evenly in a star pattern around it, each cylinder with stacked cooling fins and a domed cylinder head, and a central propeller shaft hub at the front.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="10-radial-aircraft-engine.gif" alt="9-cylinder radial aircraft engine — orbiting render" width="380"></p>
**Parametric controls:** 15 dimensions · 6 colours
**What it demonstrates**
- Nine finned cylinders in a radial star, generated as a pattern around a crankcase
- Stacked cooling fins, domed heads, and a central propeller hub
- 15 parametric controls across 6 distinct part colours
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `num_cylinders` | `9` | `[3:1:18]` | |
| `crankcase_diameter` | `100` | `[50:5:150]` | |
| `crankcase_thickness` | `40` | `[20:5:80]` | |
| `hub_diameter` | `30` | `[10:2:60]` | |
| `hub_length` | `35` | `[10:5:80]` | |
| `shaft_diameter` | `12` | `[5:1:30]` | |
| `shaft_length` | `40` | `[10:5:100]` | |
| `cylinder_diameter` | `22` | `[10:2:50]` | |
| `cylinder_length` | `60` | `[30:5:120]` | |
| `fin_thickness` | `1.2` | `[0.5:0.1:3]` | |
| `fin_spacing` | `3.5` | `[1.5:0.5:10]` | |
| `fin_overhang` | `3` | `[1:0.5:10]` | |
| `pushrod_diameter` | `3` | `[1:0.5:6]` | |
| `intake_diameter` | `4` | `[1:0.5:8]` | |
<details>
<summary>OpenSCAD source — <code>10-radial-aircraft-engine.scad</code></summary>
```scad
/* [Engine Core] */
num_cylinders = 9; // [3:1:18]
crankcase_diameter = 100; // [50:5:150]
crankcase_thickness = 40; // [20:5:80]
hub_diameter = 30; // [10:2:60]
hub_length = 35; // [10:5:80]
shaft_diameter = 12; // [5:1:30]
shaft_length = 40; // [10:5:100]
/* [Cylinders] */
cylinder_diameter = 22; // [10:2:50]
cylinder_length = 60; // [30:5:120]
fin_thickness = 1.2; // [0.5:0.1:3]
fin_spacing = 3.5; // [1.5:0.5:10]
fin_overhang = 3; // [1:0.5:10]
/* [Details] */
pushrod_diameter = 3; // [1:0.5:6]
intake_diameter = 4; // [1:0.5:8]
/* [Colors] */
crankcase_color = "SlateGray";
barrel_color = "Silver";
head_color = "DimGray";
pushrod_color = "LightGray";
intake_color = "Gray";
shaft_color = "Silver";
$fn = 64;
module engine() {
// Crankcase
color(crankcase_color) {
// Main body
cylinder(d=crankcase_diameter, h=crankcase_thickness, center=true);
// Chamfer rings to soften the drum edges
translate([0, 0, crankcase_thickness/2])
rotate_extrude() translate([crankcase_diameter/2, 0, 0]) circle(d=4);
translate([0, 0, -crankcase_thickness/2])
rotate_extrude() translate([crankcase_diameter/2, 0, 0]) circle(d=4);
// Front reduction gear housing
translate([0, 0, crankcase_thickness/2])
cylinder(d1=crankcase_diameter * 0.85, d2=hub_diameter * 1.5, h=12);
translate([0, 0, crankcase_thickness/2 + 12])
cylinder(d1=hub_diameter * 1.5, d2=hub_diameter * 1.2, h=hub_length - 12);
// Bearing housing cap
translate([0, 0, crankcase_thickness/2 + hub_length])
cylinder(d=hub_diameter * 1.25, h=5);
// Rear accessory section
translate([0, 0, -crankcase_thickness/2]) rotate([180, 0, 0])
cylinder(d1=crankcase_diameter * 0.85, d2=crankcase_diameter * 0.6, h=25);
// Crankcase ribs/bolts details
for(i=[0:num_cylinders-1]) {
rotate([0, 0, i * 360/num_cylinders + 180/num_cylinders])
translate([crankcase_diameter/2, 0, 0])
cylinder(d=8, h=crankcase_thickness, center=true);
}
}
// Prop Shaft
color(shaft_color) {
translate([0, 0, crankcase_thickness/2 + hub_length]) {
cylinder(d=shaft_diameter, h=shaft_length);
// Propeller mounting flange
translate([0, 0, shaft_length * 0.3]) {
difference() {
cylinder(d=shaft_diameter * 2.5, h=4);
// Bolt holes
for(b=[0:5]) {
rotate([0, 0, b * 360/6])
translate([shaft_diameter * 0.9, 0, -1])
cylinder(d=2, h=6);
}
}
}
translate([0, 0, shaft_length * 0.3 + 4])
cylinder(d=shaft_diameter * 1.5, h=shaft_length * 0.1);
}
}
// Cylinders and Rods
for(i=[0:num_cylinders-1]) {
angle = i * 360 / num_cylinders;
rotate([0, 0, angle]) {
// Cylinder
translate([crankcase_diameter/2 - 5, 0, 0])
rotate([0, 90, 0])
radial_cylinder();
// Pushrods (Front)
pushrod_base_x = crankcase_diameter * 0.31;
pushrod_base_z = crankcase_thickness/2 + 6;
pushrod_top_x = crankcase_diameter/2 - 5 + cylinder_length * 0.9;
pushrod_top_y = cylinder_diameter * 0.25;
pushrod_top_z = 0;
draw_rod([pushrod_base_x, -pushrod_top_y, pushrod_base_z],
[pushrod_top_x, -pushrod_top_y, pushrod_top_z],
pushrod_diameter, pushrod_color);
draw_rod([pushrod_base_x, pushrod_top_y, pushrod_base_z],
[pushrod_top_x, pushrod_top_y, pushrod_top_z],
pushrod_diameter, pushrod_color);
// Intake pipe (Back)
intake_base_x = crankcase_diameter * 0.37;
intake_base_z = -crankcase_thickness/2 - 8;
intake_top_x = crankcase_diameter/2 - 5 + cylinder_length * 0.85;
intake_top_y = 0;
intake_top_z = -cylinder_diameter * 0.4;
draw_rod([intake_base_x, 0, intake_base_z],
[intake_top_x, 0, intake_top_z],
intake_diameter, intake_color);
}
}
}
module radial_cylinder() {
// Barrel
color(barrel_color) {
// Core
cylinder(d=cylinder_diameter, h=cylinder_length * 0.7);
// Fins
num_fins = floor((cylinder_length * 0.65) / fin_spacing);
for(f=[1:num_fins]) {
translate([0, 0, f * fin_spacing + 2])
cylinder(d=cylinder_diameter + 2 * fin_overhang, h=fin_thickness, center=true);
}
}
// Head
color(head_color) {
translate([0, 0, cylinder_length * 0.7]) {
// Lower head block
cylinder(d=cylinder_diameter + 2, h=cylinder_length * 0.2);
// Domed top
translate([0, 0, cylinder_length * 0.2])
scale([1, 1, 0.7]) sphere(d=cylinder_diameter + 2);
// Rocker boxes
translate([0, cylinder_diameter * 0.25, cylinder_length * 0.2])
rotate([90, 0, 0])
cylinder(d=cylinder_diameter * 0.5, h=cylinder_diameter * 0.5, center=true);
translate([0, -cylinder_diameter * 0.25, cylinder_length * 0.2])
rotate([90, 0, 0])
cylinder(d=cylinder_diameter * 0.5, h=cylinder_diameter * 0.5, center=true);
// Head fins
head_fins = 3;
for(f=[1:head_fins]) {
translate([0, 0, f * fin_spacing - 1])
cylinder(d=cylinder_diameter + 4, h=fin_thickness, center=true);
}
}
}
}
module draw_rod(p1, p2, dia, rod_color) {
color(rod_color) {
dist = norm(p2 - p1);
dir = (p2 - p1) / dist;
axis = cross([0,0,1], dir);
angle = acos(max(-1, min(1, dir[2])));
translate(p1)
if (norm(axis) > 0.001) {
rotate(a=angle, v=axis)
cylinder(d=dia, h=dist);
} else {
cylinder(d=dia, h=dist);
}
}
}
engine();
```
</details>
@@ -1,185 +0,0 @@
/* [Engine Core] */
num_cylinders = 9; // [3:1:18]
crankcase_diameter = 100; // [50:5:150]
crankcase_thickness = 40; // [20:5:80]
hub_diameter = 30; // [10:2:60]
hub_length = 35; // [10:5:80]
shaft_diameter = 12; // [5:1:30]
shaft_length = 40; // [10:5:100]
/* [Cylinders] */
cylinder_diameter = 22; // [10:2:50]
cylinder_length = 60; // [30:5:120]
fin_thickness = 1.2; // [0.5:0.1:3]
fin_spacing = 3.5; // [1.5:0.5:10]
fin_overhang = 3; // [1:0.5:10]
/* [Details] */
pushrod_diameter = 3; // [1:0.5:6]
intake_diameter = 4; // [1:0.5:8]
/* [Colors] */
crankcase_color = "SlateGray";
barrel_color = "Silver";
head_color = "DimGray";
pushrod_color = "LightGray";
intake_color = "Gray";
shaft_color = "Silver";
$fn = 64;
module engine() {
// Crankcase
color(crankcase_color) {
// Main body
cylinder(d=crankcase_diameter, h=crankcase_thickness, center=true);
// Chamfer rings to soften the drum edges
translate([0, 0, crankcase_thickness/2])
rotate_extrude() translate([crankcase_diameter/2, 0, 0]) circle(d=4);
translate([0, 0, -crankcase_thickness/2])
rotate_extrude() translate([crankcase_diameter/2, 0, 0]) circle(d=4);
// Front reduction gear housing
translate([0, 0, crankcase_thickness/2])
cylinder(d1=crankcase_diameter * 0.85, d2=hub_diameter * 1.5, h=12);
translate([0, 0, crankcase_thickness/2 + 12])
cylinder(d1=hub_diameter * 1.5, d2=hub_diameter * 1.2, h=hub_length - 12);
// Bearing housing cap
translate([0, 0, crankcase_thickness/2 + hub_length])
cylinder(d=hub_diameter * 1.25, h=5);
// Rear accessory section
translate([0, 0, -crankcase_thickness/2]) rotate([180, 0, 0])
cylinder(d1=crankcase_diameter * 0.85, d2=crankcase_diameter * 0.6, h=25);
// Crankcase ribs/bolts details
for(i=[0:num_cylinders-1]) {
rotate([0, 0, i * 360/num_cylinders + 180/num_cylinders])
translate([crankcase_diameter/2, 0, 0])
cylinder(d=8, h=crankcase_thickness, center=true);
}
}
// Prop Shaft
color(shaft_color) {
translate([0, 0, crankcase_thickness/2 + hub_length]) {
cylinder(d=shaft_diameter, h=shaft_length);
// Propeller mounting flange
translate([0, 0, shaft_length * 0.3]) {
difference() {
cylinder(d=shaft_diameter * 2.5, h=4);
// Bolt holes
for(b=[0:5]) {
rotate([0, 0, b * 360/6])
translate([shaft_diameter * 0.9, 0, -1])
cylinder(d=2, h=6);
}
}
}
translate([0, 0, shaft_length * 0.3 + 4])
cylinder(d=shaft_diameter * 1.5, h=shaft_length * 0.1);
}
}
// Cylinders and Rods
for(i=[0:num_cylinders-1]) {
angle = i * 360 / num_cylinders;
rotate([0, 0, angle]) {
// Cylinder
translate([crankcase_diameter/2 - 5, 0, 0])
rotate([0, 90, 0])
radial_cylinder();
// Pushrods (Front)
pushrod_base_x = crankcase_diameter * 0.31;
pushrod_base_z = crankcase_thickness/2 + 6;
pushrod_top_x = crankcase_diameter/2 - 5 + cylinder_length * 0.9;
pushrod_top_y = cylinder_diameter * 0.25;
pushrod_top_z = 0;
draw_rod([pushrod_base_x, -pushrod_top_y, pushrod_base_z],
[pushrod_top_x, -pushrod_top_y, pushrod_top_z],
pushrod_diameter, pushrod_color);
draw_rod([pushrod_base_x, pushrod_top_y, pushrod_base_z],
[pushrod_top_x, pushrod_top_y, pushrod_top_z],
pushrod_diameter, pushrod_color);
// Intake pipe (Back)
intake_base_x = crankcase_diameter * 0.37;
intake_base_z = -crankcase_thickness/2 - 8;
intake_top_x = crankcase_diameter/2 - 5 + cylinder_length * 0.85;
intake_top_y = 0;
intake_top_z = -cylinder_diameter * 0.4;
draw_rod([intake_base_x, 0, intake_base_z],
[intake_top_x, 0, intake_top_z],
intake_diameter, intake_color);
}
}
}
module radial_cylinder() {
// Barrel
color(barrel_color) {
// Core
cylinder(d=cylinder_diameter, h=cylinder_length * 0.7);
// Fins
num_fins = floor((cylinder_length * 0.65) / fin_spacing);
for(f=[1:num_fins]) {
translate([0, 0, f * fin_spacing + 2])
cylinder(d=cylinder_diameter + 2 * fin_overhang, h=fin_thickness, center=true);
}
}
// Head
color(head_color) {
translate([0, 0, cylinder_length * 0.7]) {
// Lower head block
cylinder(d=cylinder_diameter + 2, h=cylinder_length * 0.2);
// Domed top
translate([0, 0, cylinder_length * 0.2])
scale([1, 1, 0.7]) sphere(d=cylinder_diameter + 2);
// Rocker boxes
translate([0, cylinder_diameter * 0.25, cylinder_length * 0.2])
rotate([90, 0, 0])
cylinder(d=cylinder_diameter * 0.5, h=cylinder_diameter * 0.5, center=true);
translate([0, -cylinder_diameter * 0.25, cylinder_length * 0.2])
rotate([90, 0, 0])
cylinder(d=cylinder_diameter * 0.5, h=cylinder_diameter * 0.5, center=true);
// Head fins
head_fins = 3;
for(f=[1:head_fins]) {
translate([0, 0, f * fin_spacing - 1])
cylinder(d=cylinder_diameter + 4, h=fin_thickness, center=true);
}
}
}
}
module draw_rod(p1, p2, dia, rod_color) {
color(rod_color) {
dist = norm(p2 - p1);
dir = (p2 - p1) / dist;
axis = cross([0,0,1], dir);
angle = acos(max(-1, min(1, dir[2])));
translate(p1)
if (norm(axis) > 0.001) {
rotate(a=angle, v=axis)
cylinder(d=dia, h=dist);
} else {
cylinder(d=dia, h=dist);
}
}
}
engine();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 KiB

-210
View File
@@ -1,210 +0,0 @@
# 11 — Turbofan jet engine
> **Prompt**
>
> A complete high-bypass turbofan: a front fan you can see into, a bypass cowl, an internal core with compressor/turbine stages, outlet guide vanes, and an exhaust plug.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="11-turbofan-jet-engine.gif" alt="Turbofan jet engine — orbiting render" width="380"></p>
**Parametric controls:** 2 dimensions · 10 colours
**What it demonstrates**
- A full high-bypass turbofan — fan, spinner, bypass cowl, core stages, guide vanes, exhaust plug
- ~26 swept fan blades visible through the intake, like the real thing
- Ten distinct part colours across the whole assembly
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `blade_count` | `26` | `[16:2:36]` | Number of front fan blades |
<details>
<summary>OpenSCAD source — <code>11-turbofan-jet-engine.scad</code></summary>
```scad
// [Engine Configuration]
// Show 1/4 cutaway to reveal internal components
cutaway_view = true;
// Number of front fan blades
blade_count = 26; // [16:2:36]
// [Colors]
nacelle_color = "White";
fan_color = "Silver";
spinner_color = "DimGray";
core_cowl_color = "DarkSlateGray";
strut_color = "LightSlateGray";
compressor_color = "LightGray";
combustor_color = "FireBrick";
turbine_color = "Peru";
exhaust_color = "SaddleBrown";
shaft_color = "LightGray";
// Main Execution
// Orient engine horizontally and center it
rotate([-90, 0, 0])
translate([0, 0, -155]) {
engine_casings();
internals();
}
// === Assemblies ===
module engine_casings() {
if (cutaway_view) {
difference() {
casings();
// 90-degree wedge cutout to reveal interior
translate([0, 0, -50])
cube([200, 200, 500]);
}
} else {
casings();
}
}
module casings() {
// Outer Nacelle Cowl
color(nacelle_color)
rotate_extrude($fn=128)
nacelle_profile();
// Inner Core Cowl
color(core_cowl_color)
rotate_extrude($fn=128)
core_cowl_profile();
// Bypass Struts / Outlet Guide Vanes
color(strut_color)
translate([0, 0, 70])
for(i = [0 : 24]) {
rotate([0, 0, i * 360 / 25])
translate([36, 0, 0])
rotate([0, 90, 0])
linear_extrude(height=42, twist=5)
rotate([0, 0, 15])
scale([1, 0.15])
circle(r=8, $fn=16);
}
}
module internals() {
// Nose Spinner
color(spinner_color)
nose_spinner();
// Front Fan Blades
color(fan_color)
translate([0, 0, 40])
for(i = [0 : blade_count - 1]) {
rotate([0, 0, i * 360 / blade_count])
translate([23, 0, 0])
rotate([-15, 0, 0])
fan_blade();
}
// Central Shaft
color(shaft_color)
rotate_extrude($fn=64)
polygon([
[0, 40], [24, 40],
[16, 55],
[16, 135],
[16, 240],
[0, 240]
]);
// Compressor Stages
color(compressor_color) {
blade_row(65, 36, 15, 33, 10);
blade_row(80, 36, 15, 31, 9);
blade_row(95, 40, 15, 29, 8);
blade_row(110, 40, 15, 27, 7);
blade_row(125, 46, 15, 25, 6);
}
// Combustor
color(combustor_color)
translate([0, 0, 135])
rotate_extrude($fn=64)
polygon([
[16, 0], [21, 5], [21, 10],
[18, 15],
[21, 20], [21, 25], [16, 30]
]);
// Turbine Stages
color(turbine_color) {
blade_row(180, 30, 15, 28, 12);
blade_row(195, 30, 15, 27.5, 12);
blade_row(210, 30, 15, 26.5, 11);
blade_row(225, 30, 15, 25.5, 10);
}
// Exhaust Plug Cone
color(exhaust_color)
translate([0, 0, 240])
cylinder(r1=16, r2=2, h=70, $fn=64);
}
// === Components ===
module nose_spinner() {
rotate_extrude($fn=64)
intersection() {
square([30, 40]);
translate([-20, 40])
circle(r=45, $fn=128);
}
}
module fan_blade() {
rotate([0, 90, 0])
linear_extrude(height=50, twist=-50, slices=30, scale=0.7)
scale([1, 0.12])
circle(r=20, $fn=32);
}
module blade_row(z_pos, count, inner_r, outer_r, chord) {
translate([0, 0, z_pos])
for(i = [0 : count - 1]) {
rotate([0, 0, i * 360 / count])
translate([inner_r - 1, 0, 0])
rotate([25, 90, 0]) // Pitch angle
linear_extrude(height = outer_r - inner_r + 2)
scale([1, 0.2])
circle(r=chord/2, $fn=16);
}
}
module nacelle_profile() {
offset(r=1.5, $fn=16)
polygon([
[76, 15], // front intake lip
[82, 50], // thickest outer curve
[78, 150], // rear outer trailing edge
[76, 150], // rear inner
[77, 80], // inner bypass wall
[75.5, 40], // fan clearance
[74.5, 20] // front inner transition
]);
}
module core_cowl_profile() {
offset(r=1, $fn=16)
polygon([
[36, 55], // front outer (behind fan)
[38, 90], // max thickness
[26, 260], // rear outer tapering nozzle
[24, 260], // rear inner nozzle lip
[36, 90], // inner max
[34, 55] // front inner (compressor intake)
]);
}
```
</details>
@@ -1,179 +0,0 @@
// [Engine Configuration]
// Show 1/4 cutaway to reveal internal components
cutaway_view = true;
// Number of front fan blades
blade_count = 26; // [16:2:36]
// [Colors]
nacelle_color = "White";
fan_color = "Silver";
spinner_color = "DimGray";
core_cowl_color = "DarkSlateGray";
strut_color = "LightSlateGray";
compressor_color = "LightGray";
combustor_color = "FireBrick";
turbine_color = "Peru";
exhaust_color = "SaddleBrown";
shaft_color = "LightGray";
// Main Execution
// Orient engine horizontally and center it
rotate([-90, 0, 0])
translate([0, 0, -155]) {
engine_casings();
internals();
}
// === Assemblies ===
module engine_casings() {
if (cutaway_view) {
difference() {
casings();
// 90-degree wedge cutout to reveal interior
translate([0, 0, -50])
cube([200, 200, 500]);
}
} else {
casings();
}
}
module casings() {
// Outer Nacelle Cowl
color(nacelle_color)
rotate_extrude($fn=128)
nacelle_profile();
// Inner Core Cowl
color(core_cowl_color)
rotate_extrude($fn=128)
core_cowl_profile();
// Bypass Struts / Outlet Guide Vanes
color(strut_color)
translate([0, 0, 70])
for(i = [0 : 24]) {
rotate([0, 0, i * 360 / 25])
translate([36, 0, 0])
rotate([0, 90, 0])
linear_extrude(height=42, twist=5)
rotate([0, 0, 15])
scale([1, 0.15])
circle(r=8, $fn=16);
}
}
module internals() {
// Nose Spinner
color(spinner_color)
nose_spinner();
// Front Fan Blades
color(fan_color)
translate([0, 0, 40])
for(i = [0 : blade_count - 1]) {
rotate([0, 0, i * 360 / blade_count])
translate([23, 0, 0])
rotate([-15, 0, 0])
fan_blade();
}
// Central Shaft
color(shaft_color)
rotate_extrude($fn=64)
polygon([
[0, 40], [24, 40],
[16, 55],
[16, 135],
[16, 240],
[0, 240]
]);
// Compressor Stages
color(compressor_color) {
blade_row(65, 36, 15, 33, 10);
blade_row(80, 36, 15, 31, 9);
blade_row(95, 40, 15, 29, 8);
blade_row(110, 40, 15, 27, 7);
blade_row(125, 46, 15, 25, 6);
}
// Combustor
color(combustor_color)
translate([0, 0, 135])
rotate_extrude($fn=64)
polygon([
[16, 0], [21, 5], [21, 10],
[18, 15],
[21, 20], [21, 25], [16, 30]
]);
// Turbine Stages
color(turbine_color) {
blade_row(180, 30, 15, 28, 12);
blade_row(195, 30, 15, 27.5, 12);
blade_row(210, 30, 15, 26.5, 11);
blade_row(225, 30, 15, 25.5, 10);
}
// Exhaust Plug Cone
color(exhaust_color)
translate([0, 0, 240])
cylinder(r1=16, r2=2, h=70, $fn=64);
}
// === Components ===
module nose_spinner() {
rotate_extrude($fn=64)
intersection() {
square([30, 40]);
translate([-20, 40])
circle(r=45, $fn=128);
}
}
module fan_blade() {
rotate([0, 90, 0])
linear_extrude(height=50, twist=-50, slices=30, scale=0.7)
scale([1, 0.12])
circle(r=20, $fn=32);
}
module blade_row(z_pos, count, inner_r, outer_r, chord) {
translate([0, 0, z_pos])
for(i = [0 : count - 1]) {
rotate([0, 0, i * 360 / count])
translate([inner_r - 1, 0, 0])
rotate([25, 90, 0]) // Pitch angle
linear_extrude(height = outer_r - inner_r + 2)
scale([1, 0.2])
circle(r=chord/2, $fn=16);
}
}
module nacelle_profile() {
offset(r=1.5, $fn=16)
polygon([
[76, 15], // front intake lip
[82, 50], // thickest outer curve
[78, 150], // rear outer trailing edge
[76, 150], // rear inner
[77, 80], // inner bypass wall
[75.5, 40], // fan clearance
[74.5, 20] // front inner transition
]);
}
module core_cowl_profile() {
offset(r=1, $fn=16)
polygon([
[36, 55], // front outer (behind fan)
[38, 90], // max thickness
[26, 260], // rear outer tapering nozzle
[24, 260], // rear inner nozzle lip
[36, 90], // inner max
[34, 55] // front inner (compressor intake)
]);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 702 KiB

-180
View File
@@ -1,180 +0,0 @@
# 12 — Axial turbine blisk
> **Prompt**
>
> Model an axial-flow turbine blisk (bladed disk) like a jet engine compressor stage: a central hub with a shaft bore and a single ring of about 28 thin aerofoil blades around the rim, each blade clearly twisted along its height from root to tip like a real turbine blade.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="12-axial-turbine-blisk.gif" alt="Axial turbine blisk — orbiting render" width="380"></p>
**Parametric controls:** 14 dimensions · 1 colour
**What it demonstrates**
- A single-stage bladed disk: a ring of twisted aerofoil blades on a central hub
- Each blade twists root-to-tip like a real turbine blade
- Keyed central shaft bore
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `num_blades` | `28` | `[10:1:60]` | Number of blades around the hub |
| `hub_radius` | `45` | `[20:1:100]` | Outer radius of the central hub |
| `bore_radius` | `12` | `[5:1:30]` | Radius of the central shaft hole |
| `blade_height` | `45` | `[20:1:100]` | Radial height of each blade |
| `hub_center_thickness` | `30` | `[10:1:60]` | Thickness of the hub at the center |
| `hub_rim_thickness` | `22` | `[10:1:50]` | Thickness of the hub at the rim |
| `flange_width` | `8` | `[0:1:20]` | Width of the collar around the bore |
| `flange_height` | `5` | `[0:1:20]` | Height of the collar around the bore |
| `blade_chord` | `22` | `[10:1:50]` | Length of the blade profile (chord) |
| `root_stagger` | `30` | `[0:1:90]` | Angle of the blade at the root |
| `blade_twist` | `35` | `[-60:1:60]` | Total twist from root to tip |
| `camber_pct` | `10` | `[0:1:20]` | Aerodynamic camber (curvature) |
| `thickness_pct` | `6` | `[2:1:15]` | Maximum thickness of the blade profile |
| `steps` | `30` | `Resolution per side` | |
<details>
<summary>OpenSCAD source — <code>12-axial-turbine-blisk.scad</code></summary>
```scad
// Axial-Flow Turbine Blisk
// A parametric model of a jet engine compressor stage
/* [Blisk Dimensions] */
// Number of blades around the hub
num_blades = 28; // [10:1:60]
// Outer radius of the central hub
hub_radius = 45; // [20:1:100]
// Radius of the central shaft hole
bore_radius = 12; // [5:1:30]
// Radial height of each blade
blade_height = 45; // [20:1:100]
/* [Hub Profile] */
// Thickness of the hub at the center
hub_center_thickness = 30; // [10:1:60]
// Thickness of the hub at the rim
hub_rim_thickness = 22; // [10:1:50]
// Width of the collar around the bore
flange_width = 8; // [0:1:20]
// Height of the collar around the bore
flange_height = 5; // [0:1:20]
/* [Blade Aerodynamics] */
// Length of the blade profile (chord)
blade_chord = 22; // [10:1:50]
// Angle of the blade at the root
root_stagger = 30; // [0:1:90]
// Total twist from root to tip
blade_twist = 35; // [-60:1:60]
// Aerodynamic camber (curvature)
camber_pct = 10; // [0:1:20]
// Maximum thickness of the blade profile
thickness_pct = 6; // [2:1:15]
/* [Settings] */
blisk_color = "#A8AEB3";
$fn = 72;
// --- Main Model ---
color(blisk_color)
difference() {
union() {
hub();
blade_array();
}
// Central bore keyway to lock the blisk to a shaft
translate([bore_radius + 1.5, 0, 0])
cube([4, 6, hub_center_thickness * 3], center=true);
}
// --- Modules ---
module hub() {
// Generates the profiled central disk
rotate_extrude(convexity = 4)
polygon([
[bore_radius, hub_center_thickness/2 + flange_height],
[bore_radius + flange_width, hub_center_thickness/2 + flange_height],
[bore_radius + flange_width, hub_center_thickness/2],
[hub_radius, hub_rim_thickness/2],
[hub_radius, -hub_rim_thickness/2],
[bore_radius + flange_width, -hub_center_thickness/2],
[bore_radius + flange_width, -hub_center_thickness/2 - flange_height],
[bore_radius, -hub_center_thickness/2 - flange_height]
]);
}
module blade_array() {
// Radial array of aerofoil blades
for (i = [0 : num_blades - 1]) {
rotate([0, 0, i * 360 / num_blades])
// Embed the blade root slightly into the hub rim
translate([hub_radius - 2, 0, 0])
// Lay the blade down so it points outward radially
rotate([0, 90, 0])
// Extrude with aerodynamic twist
linear_extrude(height = blade_height + 2, twist = blade_twist, slices = 45, convexity = 2)
// Set the root angle (stagger)
rotate([0, 0, root_stagger])
// Center the aerofoil on the extrusion axis
translate([-blade_chord/2, 0])
naca_airfoil(blade_chord, thickness_pct, camber_pct, 40);
}
}
module naca_airfoil(c, t_pct, m_pct, p_pct) {
// Generates a robust NACA-style highly cambered profile
t_val = t_pct / 100;
m = m_pct / 100;
p = p_pct / 100;
// Thickness distribution function
function y_t(x) = 5 * t_val * c * (0.2969*sqrt(abs(x/c)) - 0.1260*(x/c) - 0.3516*pow(x/c,2) + 0.2843*pow(x/c,3) - 0.1015*pow(x/c,4));
// Camber line function
function y_c(x) = (m == 0) ? 0 :
( (x/c) <= p ) ? m * c / pow(p,2) * (2*p*(x/c) - pow(x/c,2))
: m * c / pow(1-p,2) * ((1-2*p) + 2*p*(x/c) - pow(x/c,2));
// Camber gradient for normal vector
function dy_c(x) = (m == 0) ? 0 :
( (x/c) <= p ) ? 2 * m / pow(p,2) * (p - (x/c))
: 2 * m / pow(1-p,2) * (p - (x/c));
function theta(x) = atan(dy_c(x));
steps = 30; // Resolution per side
// Generate upper surface points
pts_upper = [ for (i=[0:steps])
let (
x = c * (1 - cos(i * 180 / steps))/2,
yt = y_t(x),
yc = y_c(x),
th = theta(x)
)
[ x - yt * sin(th), yc + yt * cos(th) ]
];
// Generate lower surface points
pts_lower = [ for (i=[steps:-1:0])
let (
x = c * (1 - cos(i * 180 / steps))/2,
yt = y_t(x),
yc = y_c(x),
th = theta(x)
)
[ x + yt * sin(th), yc - yt * cos(th) ]
];
// Combine and apply offset to guarantee manifold trailing edges and robust printability
offset(r=0.6)
polygon(concat(pts_upper, pts_lower));
}
```
</details>
@@ -1,136 +0,0 @@
// Axial-Flow Turbine Blisk
// A parametric model of a jet engine compressor stage
/* [Blisk Dimensions] */
// Number of blades around the hub
num_blades = 28; // [10:1:60]
// Outer radius of the central hub
hub_radius = 45; // [20:1:100]
// Radius of the central shaft hole
bore_radius = 12; // [5:1:30]
// Radial height of each blade
blade_height = 45; // [20:1:100]
/* [Hub Profile] */
// Thickness of the hub at the center
hub_center_thickness = 30; // [10:1:60]
// Thickness of the hub at the rim
hub_rim_thickness = 22; // [10:1:50]
// Width of the collar around the bore
flange_width = 8; // [0:1:20]
// Height of the collar around the bore
flange_height = 5; // [0:1:20]
/* [Blade Aerodynamics] */
// Length of the blade profile (chord)
blade_chord = 22; // [10:1:50]
// Angle of the blade at the root
root_stagger = 30; // [0:1:90]
// Total twist from root to tip
blade_twist = 35; // [-60:1:60]
// Aerodynamic camber (curvature)
camber_pct = 10; // [0:1:20]
// Maximum thickness of the blade profile
thickness_pct = 6; // [2:1:15]
/* [Settings] */
blisk_color = "#A8AEB3";
$fn = 72;
// --- Main Model ---
color(blisk_color)
difference() {
union() {
hub();
blade_array();
}
// Central bore keyway to lock the blisk to a shaft
translate([bore_radius + 1.5, 0, 0])
cube([4, 6, hub_center_thickness * 3], center=true);
}
// --- Modules ---
module hub() {
// Generates the profiled central disk
rotate_extrude(convexity = 4)
polygon([
[bore_radius, hub_center_thickness/2 + flange_height],
[bore_radius + flange_width, hub_center_thickness/2 + flange_height],
[bore_radius + flange_width, hub_center_thickness/2],
[hub_radius, hub_rim_thickness/2],
[hub_radius, -hub_rim_thickness/2],
[bore_radius + flange_width, -hub_center_thickness/2],
[bore_radius + flange_width, -hub_center_thickness/2 - flange_height],
[bore_radius, -hub_center_thickness/2 - flange_height]
]);
}
module blade_array() {
// Radial array of aerofoil blades
for (i = [0 : num_blades - 1]) {
rotate([0, 0, i * 360 / num_blades])
// Embed the blade root slightly into the hub rim
translate([hub_radius - 2, 0, 0])
// Lay the blade down so it points outward radially
rotate([0, 90, 0])
// Extrude with aerodynamic twist
linear_extrude(height = blade_height + 2, twist = blade_twist, slices = 45, convexity = 2)
// Set the root angle (stagger)
rotate([0, 0, root_stagger])
// Center the aerofoil on the extrusion axis
translate([-blade_chord/2, 0])
naca_airfoil(blade_chord, thickness_pct, camber_pct, 40);
}
}
module naca_airfoil(c, t_pct, m_pct, p_pct) {
// Generates a robust NACA-style highly cambered profile
t_val = t_pct / 100;
m = m_pct / 100;
p = p_pct / 100;
// Thickness distribution function
function y_t(x) = 5 * t_val * c * (0.2969*sqrt(abs(x/c)) - 0.1260*(x/c) - 0.3516*pow(x/c,2) + 0.2843*pow(x/c,3) - 0.1015*pow(x/c,4));
// Camber line function
function y_c(x) = (m == 0) ? 0 :
( (x/c) <= p ) ? m * c / pow(p,2) * (2*p*(x/c) - pow(x/c,2))
: m * c / pow(1-p,2) * ((1-2*p) + 2*p*(x/c) - pow(x/c,2));
// Camber gradient for normal vector
function dy_c(x) = (m == 0) ? 0 :
( (x/c) <= p ) ? 2 * m / pow(p,2) * (p - (x/c))
: 2 * m / pow(1-p,2) * (p - (x/c));
function theta(x) = atan(dy_c(x));
steps = 30; // Resolution per side
// Generate upper surface points
pts_upper = [ for (i=[0:steps])
let (
x = c * (1 - cos(i * 180 / steps))/2,
yt = y_t(x),
yc = y_c(x),
th = theta(x)
)
[ x - yt * sin(th), yc + yt * cos(th) ]
];
// Generate lower surface points
pts_lower = [ for (i=[steps:-1:0])
let (
x = c * (1 - cos(i * 180 / steps))/2,
yt = y_t(x),
yc = y_c(x),
th = theta(x)
)
[ x + yt * sin(th), yc - yt * cos(th) ]
];
// Combine and apply offset to guarantee manifold trailing edges and robust printability
offset(r=0.6)
polygon(concat(pts_upper, pts_lower));
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

-490
View File
@@ -1,490 +0,0 @@
# 13 — V8 engine
> **Prompt**
>
> A complete V8 internal combustion engine: two banks of four cylinders in a 90° V, cylinder heads with ribbed valve covers, an intake manifold in the valley, exhaust headers down each bank, a crankshaft with counterweights, pistons and connecting rods, a front pulley, and an oil pan.
Fully parametric OpenSCAD built from the prompt above — adjustable dimensions and colours, exported as `.SCAD` and rendered to the orbiting preview with [`render.sh`](render.sh).
<p align="center"><img src="13-v8-engine.gif" alt="V8 engine — orbiting render" width="380"></p>
**Parametric controls:** 22 dimensions · 8 colours
**What it demonstrates**
- A complete V8: 90° cylinder banks, heads, ribbed valve covers, intake manifold, exhaust headers, and oil pan
- Even the internal rotating assembly — crankshaft with counterweights, pistons, and connecting rods — modelled inside the block
- 22 parametric controls across 8 part colours; ~460 lines of OpenSCAD — the most complex benchmark here
**Editable parameters**
| Parameter | Default | Range / options | Description |
| --- | --- | --- | --- |
| `crank_angle` | `45` | `[0:1:360]` | Rotate the crankshaft to see the pistons move |
<details>
<summary>OpenSCAD source — <code>13-v8-engine.scad</code></summary>
```scad
// V8 Engine Model
/* [Visibility] */
// Enable cutaway view to see internal mechanics
cutaway = true;
/* [Animation] */
// Rotate the crankshaft to see the pistons move
crank_angle = 45; // [0:1:360]
/* [Colors] */
block_color = "Silver";
head_color = "DarkGray";
valve_cover_color = "FireBrick";
intake_color = "DimGray";
exhaust_color = "Peru";
pulley_color = "Black";
oil_pan_color = "DarkSlateGray";
internals_color = "LightSteelBlue";
/* [Hidden] */
$fn = 32;
bore = 10;
stroke = 15;
crank_r = stroke / 2;
conrod_len = 35;
cyl_spacing = 28;
deck_height = 50;
bank_angle = 45;
pin_angles = [0, 90, 270, 180];
main_y = [0, 28, 56, 84, 112];
pin_y_start = [8, 36, 64, 92];
// Core kinematics calculation for piston position
function get_D(crank_ang, bank_ang) =
let(Px = crank_r * sin(crank_ang),
Pz = crank_r * cos(crank_ang),
K = Px * sin(bank_ang) + Pz * cos(bank_ang))
K + sqrt(K*K - crank_r*crank_r + conrod_len*conrod_len);
module assemble_engine() {
engine_stand();
engine_block();
heads();
valve_covers();
spark_plugs();
oil_pan();
intake_manifold();
exhaust_headers();
front_accessories();
alternator();
belt();
front_pulley();
fan();
flywheel();
internals(crank_angle);
}
module cutaway_cutter() {
if(cutaway) {
intersection() {
// Diagonal cut plane along cylinder axis
translate([0, 50, 0])
rotate([0, bank_angle, 0])
translate([0.1, 0, -50])
cube([80, 100, 150]);
// Limit strictly to right half to preserve left side
translate([0, 50, -50]) cube([100, 100, 150]);
}
}
}
module engine_block() {
color(block_color)
difference() {
union() {
// Main block body
translate([-25, 0, -10]) cube([50, 120, 20]);
// Banks
intersect_bank(bank_angle);
intersect_bank(-bank_angle);
// Side reinforcement ribs
for(y = main_y) {
translate([-26, y-2, -10]) cube([52, 4, 15]);
}
// Rear bell housing flange
translate([-26, -4, -10]) cube([52, 4, 30]);
}
// Hollow out valley
translate([-10, -1, 20]) cube([20, 122, 40]);
// Hollow out crankcase
translate([0, -1, 0]) rotate([-90,0,0]) cylinder(r=18, h=122);
translate([-18, -1, -20]) cube([36, 122, 20]);
// Cylinder bores
for(i=[0:3]) {
y_s = pin_y_start[i];
// Right bank
translate([0, y_s + 7.5, 0])
rotate([0, bank_angle, 0])
translate([0, 0, 10]) cylinder(r=bore, h=deck_height+10);
// Left bank
translate([0, y_s + 12.5, 0])
rotate([0, -bank_angle, 0])
translate([0, 0, 10]) cylinder(r=bore, h=deck_height+10);
}
cutaway_cutter();
}
}
module intersect_bank(ang) {
rotate([0, ang, 0])
translate([-14, 0, -20]) cube([28, 120, deck_height + 20]);
}
module heads() {
color(head_color)
difference() {
union() {
head_shape(bank_angle);
head_shape(-bank_angle);
}
cutaway_cutter();
}
}
module head_shape(ang) {
rotate([0, ang, 0])
translate([-14, 0, deck_height])
difference() {
cube([28, 120, 18]);
// Spark plug recesses
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
translate([14, y_s, 9]) rotate([0, 90, 0]) cylinder(r=4, h=10, center=true);
}
}
}
module spark_plugs() {
color("White")
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
// Right
rotate([0, bank_angle, 0]) translate([14, y_s, deck_height + 9]) rotate([0, 90, 0]) cylinder(r=2, h=8);
// Left
rotate([0, -bank_angle, 0]) translate([-14, y_s, deck_height + 9]) rotate([0, -90, 0]) cylinder(r=2, h=8);
}
}
module valve_covers() {
color(valve_cover_color)
difference() {
union() {
valve_cover_shape(bank_angle);
valve_cover_shape(-bank_angle);
}
cutaway_cutter();
}
}
module valve_cover_shape(ang) {
rotate([0, ang, 0])
translate([-12, 0, deck_height + 18]) {
// Base
hull() {
cube([24, 120, 2]);
translate([2, 2, 8]) cube([20, 116, 2]);
}
// Ribs
for(y=[5 : 10 : 115]) {
translate([4, y, 10]) cube([16, 4, 2]);
}
// Oil cap
if(ang > 0) {
translate([12, 20, 10]) cylinder(r=5, h=4);
}
}
}
module oil_pan() {
color(oil_pan_color)
difference() {
union() {
hull() {
translate([-25, 0, -10]) cube([50, 120, 1]);
translate([-15, 10, -30]) cube([30, 100, 1]);
}
for(y=[15:5:105]) {
translate([-15, y, -32]) cube([30, 2, 4]);
}
}
// Hollow inside
hull() {
translate([-23, 2, -10]) cube([46, 116, 1]);
translate([-13, 12, -28]) cube([26, 96, 1]);
}
cutaway_cutter();
}
}
module intake_manifold() {
color(intake_color)
difference() {
union() {
// Central plenum
translate([-10, 10, 35]) cube([20, 100, 15]);
// Runners to Right head
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
hull() {
translate([10, y_s-4, 40]) cube([2, 8, 8]);
rotate([0, bank_angle, 0]) translate([-14, y_s-4, deck_height-2]) cube([5, 8, 12]);
}
}
// Runners to Left head
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
hull() {
translate([-12, y_s-4, 40]) cube([2, 8, 8]);
rotate([0, -bank_angle, 0]) translate([9, y_s-4, deck_height-2]) cube([5, 8, 12]);
}
}
// Throttle body
translate([0, 110, 42.5]) rotate([-90,0,0]) cylinder(r=8, h=15);
}
cutaway_cutter();
}
}
module exhaust_headers() {
color(exhaust_color)
difference() {
union() {
// Right bank
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
header_pipe(bank_angle, 1, y_s);
}
// Right Collector
translate([50, 10, -25]) rotate([-90,0,0]) cylinder(r=6, h=100);
// Left bank
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
header_pipe(-bank_angle, -1, y_s);
}
// Left Collector
translate([-50, 10, -25]) rotate([-90,0,0]) cylinder(r=6, h=100);
}
cutaway_cutter();
}
}
module header_pipe(ang, dir, y) {
gx = dir * (14*cos(45) + (deck_height+7.5)*sin(45));
gz = (deck_height+7.5)*cos(45) - 14*sin(45);
hull() {
translate([gx, y, gz]) rotate([0, 90, 0]) cylinder(r=4, h=2, center=true);
translate([gx + dir*5, y, gz - 5]) sphere(r=4);
}
hull() {
translate([gx + dir*5, y, gz - 5]) sphere(r=4);
translate([dir*50, y, -25]) sphere(r=5);
}
}
module front_accessories() {
color(block_color)
difference() {
union() {
// Timing cover
hull() {
translate([-25, 120, -10]) cube([50, 4, 20]);
translate([-15, 120, 40]) cube([30, 4, 10]);
}
// Water pump
translate([0, 124, 30]) rotate([-90,0,0]) cylinder(r=12, h=8);
// Water pump pulley
color(pulley_color) translate([0, 132, 30]) rotate([-90,0,0]) {
cylinder(r=10, h=8);
translate([0,0,8]) cylinder(r=8, h=2);
}
}
cutaway_cutter();
}
}
module alternator() {
color("LightGray")
difference() {
union() {
translate([20, 125, 40]) rotate([-90,0,0]) cylinder(r=8, h=10);
color(pulley_color) translate([20, 135, 40]) rotate([-90,0,0]) cylinder(r=6, h=5);
}
cutaway_cutter();
}
}
module belt() {
color("#222222")
difference() {
hull() {
translate([0, 134, 30]) rotate([-90,0,0]) cylinder(r=10, h=4);
translate([0, 134, 0]) rotate([-90,0,0]) cylinder(r=16, h=4);
translate([20, 135, 40]) rotate([-90,0,0]) cylinder(r=6, h=4);
}
hull() {
translate([0, 133, 30]) rotate([-90,0,0]) cylinder(r=8, h=6);
translate([0, 133, 0]) rotate([-90,0,0]) cylinder(r=14, h=6);
translate([20, 134, 40]) rotate([-90,0,0]) cylinder(r=4, h=6);
}
cutaway_cutter();
}
}
module fan() {
color("Silver")
difference() {
union() {
translate([0, 142, 30]) rotate([-90,0,0]) cylinder(r=5, h=2);
for(a=[0:72:359]) {
rotate([0, a, 0])
translate([5, 142.5, 30])
rotate([20, 0, 0])
cube([25, 2, 8]);
}
}
cutaway_cutter();
}
}
module front_pulley() {
color(pulley_color)
difference() {
translate([0, 124, 0]) rotate([-90,0,0]) {
cylinder(r=12, h=8);
translate([0,0,8]) cylinder(r=16, h=8);
}
cutaway_cutter();
}
}
module flywheel() {
color(pulley_color)
translate([0, -5, 0]) rotate([-90,0,0]) cylinder(r=25, h=5);
}
module engine_stand() {
color("Orange")
union() {
// Floor base
translate([-40, -40, -60]) cube([80, 160, 5]);
// Upright
translate([-15, -30, -55]) cube([30, 15, 75]);
// Stand head
translate([-10, -15, -10]) cube([20, 10, 20]);
// Standoff mounts to bell housing
for(x=[-15, 15]) {
for(z=[-5, 15]) {
translate([x, -15, z]) rotate([-90,0,0]) cylinder(r=2, h=15);
}
}
}
}
module internals(crank_angle_offset=0) {
color(internals_color) {
// Main journals
for(y = main_y) {
translate([0, y, 0]) {
translate([0, 4, 0]) rotate([-90,0,0]) cylinder(r=5, h=8, center=true);
}
}
for(i = [0:3]) {
ang = crank_angle_offset + pin_angles[i];
y_s = pin_y_start[i];
// Web 1
web(ang, y_s);
// Crank Pin
translate([0, y_s + 5, 0])
rotate([0, ang, 0])
translate([0, 0, crank_r])
translate([0, 5, 0]) rotate([-90,0,0]) cylinder(r=4, h=10, center=true);
// Web 2
web(ang, y_s + 15);
// Piston 1 (Right Bank)
piston_and_rod(ang, bank_angle, y_s + 7.5);
// Piston 2 (Left Bank)
piston_and_rod(ang, -bank_angle, y_s + 12.5);
}
}
}
module web(angle, y_pos) {
translate([0, y_pos, 0])
rotate([0, angle, 0]) {
hull() {
translate([0, 2.5, 0]) rotate([-90,0,0]) cylinder(r=6, h=5, center=true);
translate([0, 2.5, crank_r]) rotate([-90,0,0]) cylinder(r=6, h=5, center=true);
}
// Counterweight
translate([0, 2.5, -crank_r/2 + 1])
cube([16, 5, crank_r + 10], center=true);
}
}
module piston_and_rod(crank_ang, bank_ang, y_pos) {
D = get_D(crank_ang, bank_ang);
Px = crank_r * sin(crank_ang);
Pz = crank_r * cos(crank_ang);
Wx = D * sin(bank_ang);
Wz = D * cos(bank_ang);
rod_ang = atan2(Wx - Px, Wz - Pz);
translate([0, y_pos, 0]) {
// Conrod
translate([Px, 0, Pz])
rotate([0, rod_ang, 0])
translate([0, -2.5, 0]) {
hull() {
rotate([-90,0,0]) cylinder(r=4, h=5);
translate([0, 0, conrod_len]) rotate([-90,0,0]) cylinder(r=3, h=5);
}
}
// Piston pin
translate([Wx, 0, Wz])
rotate([0, bank_ang, 0])
rotate([-90,0,0]) cylinder(r=2.5, h=10, center=true);
// Piston body
translate([Wx, 0, Wz])
rotate([0, bank_ang, 0]) {
translate([0, 0, -6]) rotate([-90,0,0]) cylinder(r=bore-0.5, h=12, center=true);
translate([0, 0, 6]) rotate([-90,0,0]) cylinder(r=bore-0.5, h=4, center=true);
}
}
}
assemble_engine();
```
</details>
-472
View File
@@ -1,472 +0,0 @@
// V8 Engine Model
/* [Visibility] */
// Enable cutaway view to see internal mechanics
cutaway = true;
/* [Animation] */
// Rotate the crankshaft to see the pistons move
crank_angle = 45; // [0:1:360]
/* [Colors] */
block_color = "Silver";
head_color = "DarkGray";
valve_cover_color = "FireBrick";
intake_color = "DimGray";
exhaust_color = "Peru";
pulley_color = "Black";
oil_pan_color = "DarkSlateGray";
internals_color = "LightSteelBlue";
/* [Hidden] */
$fn = 32;
bore = 10;
stroke = 15;
crank_r = stroke / 2;
conrod_len = 35;
cyl_spacing = 28;
deck_height = 50;
bank_angle = 45;
pin_angles = [0, 90, 270, 180];
main_y = [0, 28, 56, 84, 112];
pin_y_start = [8, 36, 64, 92];
// Core kinematics calculation for piston position
function get_D(crank_ang, bank_ang) =
let(Px = crank_r * sin(crank_ang),
Pz = crank_r * cos(crank_ang),
K = Px * sin(bank_ang) + Pz * cos(bank_ang))
K + sqrt(K*K - crank_r*crank_r + conrod_len*conrod_len);
module assemble_engine() {
engine_stand();
engine_block();
heads();
valve_covers();
spark_plugs();
oil_pan();
intake_manifold();
exhaust_headers();
front_accessories();
alternator();
belt();
front_pulley();
fan();
flywheel();
internals(crank_angle);
}
module cutaway_cutter() {
if(cutaway) {
intersection() {
// Diagonal cut plane along right cylinder axis
rotate([0, bank_angle, 0])
// Massive bounding box starting at main journal 3 to cleanly slice front-right
translate([0.1, 56, -150])
cube([300, 200, 400]);
// Limit strictly to right side of the engine to preserve left bank
translate([0.1, 56, -150]) cube([300, 200, 400]);
}
}
}
module engine_block() {
color(block_color)
difference() {
union() {
// Main block body
translate([-25, 0, -10]) cube([50, 120, 20]);
// Banks
intersect_bank(bank_angle);
intersect_bank(-bank_angle);
// Side reinforcement ribs
for(y = main_y) {
translate([-26, y-2, -10]) cube([52, 4, 15]);
}
// Rear bell housing flange
translate([-26, -4, -10]) cube([52, 4, 30]);
// Main bearing supports (bulkheads) connecting block to crank
for(y = main_y) {
translate([-15, y, -15]) cube([30, 8, 15]);
}
}
// Hollow out valley
translate([-10, -1, 20]) cube([20, 122, 40]);
// Hollow out crankcase bays between the bulkheads
for(i = [0:3]) {
y_start_hollow = main_y[i] + 8;
hollow_len = main_y[i+1] - y_start_hollow;
if (hollow_len > 0) {
translate([0, y_start_hollow, 0]) rotate([-90,0,0]) cylinder(r=18, h=hollow_len);
translate([-18, y_start_hollow, -20]) cube([36, hollow_len, 20]);
}
}
// Front and rear crank clearance
translate([0, -5, 0]) rotate([-90,0,0]) cylinder(r=18, h=5);
translate([-18, -5, -20]) cube([36, 5, 20]);
translate([0, 120, 0]) rotate([-90,0,0]) cylinder(r=18, h=5);
translate([-18, 120, -20]) cube([36, 5, 20]);
// Cut the shaft hole for the crank through the solid bulkheads perfectly matching radius
translate([0, -10, 0]) rotate([-90,0,0]) cylinder(r=5, h=140);
// Cylinder bores
for(i=[0:3]) {
y_s = pin_y_start[i];
// Right bank
translate([0, y_s + 7.5, 0])
rotate([0, bank_angle, 0])
translate([0, 0, 10]) cylinder(r=bore, h=deck_height+10);
// Left bank
translate([0, y_s + 12.5, 0])
rotate([0, -bank_angle, 0])
translate([0, 0, 10]) cylinder(r=bore, h=deck_height+10);
}
cutaway_cutter();
}
}
module intersect_bank(ang) {
rotate([0, ang, 0])
translate([-14, 0, -20]) cube([28, 120, deck_height + 20]);
}
module heads() {
color(head_color)
difference() {
union() {
head_shape(bank_angle);
head_shape(-bank_angle);
}
cutaway_cutter();
}
}
module head_shape(ang) {
rotate([0, ang, 0])
translate([-14, 0, deck_height])
difference() {
cube([28, 120, 18]);
// Spark plug recesses
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
translate([14, y_s, 9]) rotate([0, 90, 0]) cylinder(r=4, h=10, center=true);
}
}
}
module spark_plugs() {
color("White")
difference() {
union() {
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
// Right
rotate([0, bank_angle, 0]) translate([14, y_s, deck_height + 9]) rotate([0, 90, 0]) cylinder(r=2, h=8);
// Left
rotate([0, -bank_angle, 0]) translate([-14, y_s, deck_height + 9]) rotate([0, -90, 0]) cylinder(r=2, h=8);
}
}
cutaway_cutter();
}
}
module valve_covers() {
color(valve_cover_color)
difference() {
union() {
valve_cover_shape(bank_angle);
valve_cover_shape(-bank_angle);
}
cutaway_cutter();
}
}
module valve_cover_shape(ang) {
rotate([0, ang, 0])
translate([-12, 0, deck_height + 18]) {
// Base
hull() {
cube([24, 120, 2]);
translate([2, 2, 8]) cube([20, 116, 2]);
}
// Ribs
for(y=[5 : 10 : 115]) {
translate([4, y, 10]) cube([16, 4, 2]);
}
// Oil cap
if(ang > 0) {
translate([12, 20, 10]) cylinder(r=5, h=4);
}
}
}
module oil_pan() {
color(oil_pan_color)
difference() {
union() {
hull() {
translate([-25, 0, -10]) cube([50, 120, 1]);
translate([-15, 10, -30]) cube([30, 100, 1]);
}
for(y=[15:5:105]) {
translate([-15, y, -32]) cube([30, 2, 4]);
}
}
// Hollow inside
hull() {
translate([-23, 2, -10]) cube([46, 116, 1]);
translate([-13, 12, -28]) cube([26, 96, 1]);
}
cutaway_cutter();
}
}
module intake_manifold() {
color(intake_color)
difference() {
union() {
// Central plenum
translate([-10, 10, 35]) cube([20, 100, 15]);
// Runners to Right head
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
hull() {
translate([10, y_s-4, 40]) cube([2, 8, 8]);
rotate([0, bank_angle, 0]) translate([-14, y_s-4, deck_height-2]) cube([5, 8, 12]);
}
}
// Runners to Left head
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
hull() {
translate([-12, y_s-4, 40]) cube([2, 8, 8]);
rotate([0, -bank_angle, 0]) translate([9, y_s-4, deck_height-2]) cube([5, 8, 12]);
}
}
// Throttle body
translate([0, 110, 42.5]) rotate([-90,0,0]) cylinder(r=8, h=15);
}
cutaway_cutter();
}
}
module exhaust_headers() {
color(exhaust_color)
difference() {
union() {
// Right bank
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
header_pipe(bank_angle, 1, y_s);
}
// Right Collector
translate([50, 10, -25]) rotate([-90,0,0]) cylinder(r=6, h=100);
// Left bank
for(i=[0:3]) {
y_s = pin_y_start[i] + 10;
header_pipe(-bank_angle, -1, y_s);
}
// Left Collector
translate([-50, 10, -25]) rotate([-90,0,0]) cylinder(r=6, h=100);
}
cutaway_cutter();
}
}
module header_pipe(ang, dir, y) {
gx = dir * (14*cos(45) + (deck_height+7.5)*sin(45));
gz = (deck_height+7.5)*cos(45) - 14*sin(45);
hull() {
translate([gx, y, gz]) rotate([0, 90, 0]) cylinder(r=4, h=2, center=true);
translate([gx + dir*5, y, gz - 5]) sphere(r=4);
}
hull() {
translate([gx + dir*5, y, gz - 5]) sphere(r=4);
translate([dir*50, y, -25]) sphere(r=5);
}
}
module front_accessories() {
color(block_color)
union() {
// Timing cover
hull() {
translate([-25, 120, -10]) cube([50, 4, 20]);
translate([-15, 120, 40]) cube([30, 4, 10]);
}
// Water pump
translate([0, 124, 30]) rotate([-90,0,0]) cylinder(r=12, h=8);
// Water pump pulley
color(pulley_color) translate([0, 132, 30]) rotate([-90,0,0]) {
cylinder(r=10, h=8);
translate([0,0,8]) cylinder(r=8, h=2);
}
}
}
module alternator() {
color("LightGray")
union() {
translate([20, 125, 40]) rotate([-90,0,0]) cylinder(r=8, h=10);
color(pulley_color) translate([20, 135, 40]) rotate([-90,0,0]) cylinder(r=6, h=5);
}
}
module belt() {
color("#222222")
difference() {
hull() {
translate([0, 134, 30]) rotate([-90,0,0]) cylinder(r=10, h=4);
translate([0, 134, 0]) rotate([-90,0,0]) cylinder(r=16, h=4);
translate([20, 135, 40]) rotate([-90,0,0]) cylinder(r=6, h=4);
}
hull() {
translate([0, 133, 30]) rotate([-90,0,0]) cylinder(r=8, h=6);
translate([0, 133, 0]) rotate([-90,0,0]) cylinder(r=14, h=6);
translate([20, 134, 40]) rotate([-90,0,0]) cylinder(r=4, h=6);
}
}
}
module fan() {
color("Silver")
union() {
translate([0, 142, 30]) rotate([-90,0,0]) cylinder(r=5, h=2);
// Fan blades removed per user request
}
}
module front_pulley() {
color(pulley_color)
translate([0, 124, 0]) rotate([-90,0,0]) {
cylinder(r=12, h=8);
translate([0,0,8]) cylinder(r=16, h=8);
}
}
module flywheel() {
color(pulley_color)
translate([0, -5, 0]) rotate([-90,0,0]) cylinder(r=25, h=5);
}
module engine_stand() {
color("Orange")
union() {
// Floor base
translate([-40, -40, -60]) cube([80, 160, 5]);
// Upright
translate([-15, -30, -55]) cube([30, 15, 75]);
// Stand head
translate([-10, -15, -10]) cube([20, 10, 20]);
// Standoff mounts to bell housing
for(x=[-15, 15]) {
for(z=[-5, 15]) {
translate([x, -15, z]) rotate([-90,0,0]) cylinder(r=2, h=15);
}
}
}
}
module internals(crank_angle_offset=0) {
color(internals_color) {
// Front extension connecting to front pulley
translate([0, 120, 0]) rotate([-90,0,0]) cylinder(r=5, h=20);
// Rear extension connecting to flywheel
translate([0, -10, 0]) rotate([-90,0,0]) cylinder(r=5, h=10);
// Main journals nested in bulkheads
for(y = main_y) {
translate([0, y, 0]) {
translate([0, 4, 0]) rotate([-90,0,0]) cylinder(r=5, h=8, center=true);
}
}
for(i = [0:3]) {
ang = crank_angle_offset + pin_angles[i];
y_s = pin_y_start[i];
// Web 1
web(ang, y_s);
// Crank Pin
translate([0, y_s + 5, 0])
rotate([0, ang, 0])
translate([0, 0, crank_r])
translate([0, 5, 0]) rotate([-90,0,0]) cylinder(r=4, h=10, center=true);
// Web 2
web(ang, y_s + 15);
// Piston 1 (Right Bank)
piston_and_rod(ang, bank_angle, y_s + 7.5);
// Piston 2 (Left Bank)
piston_and_rod(ang, -bank_angle, y_s + 12.5);
}
}
}
module web(angle, y_pos) {
translate([0, y_pos, 0])
rotate([0, angle, 0]) {
hull() {
translate([0, 2.5, 0]) rotate([-90,0,0]) cylinder(r=6, h=5, center=true);
translate([0, 2.5, crank_r]) rotate([-90,0,0]) cylinder(r=6, h=5, center=true);
}
// Counterweight
translate([0, 2.5, -crank_r/2 + 1])
cube([16, 5, crank_r + 10], center=true);
}
}
module piston_and_rod(crank_ang, bank_ang, y_pos) {
D = get_D(crank_ang, bank_ang);
Px = crank_r * sin(crank_ang);
Pz = crank_r * cos(crank_ang);
Wx = D * sin(bank_ang);
Wz = D * cos(bank_ang);
rod_ang = atan2(Wx - Px, Wz - Pz);
translate([0, y_pos, 0]) {
// Conrod
translate([Px, 0, Pz])
rotate([0, rod_ang, 0])
translate([0, -2.5, 0]) {
hull() {
rotate([-90,0,0]) cylinder(r=4, h=5);
translate([0, 0, conrod_len]) rotate([-90,0,0]) cylinder(r=3, h=5);
}
}
// Piston pin
translate([Wx, 0, Wz])
rotate([0, bank_ang, 0])
rotate([-90,0,0]) cylinder(r=2.5, h=10, center=true);
// Piston body
translate([Wx, 0, Wz])
rotate([0, bank_ang, 0]) {
translate([0, 0, -6]) rotate([-90,0,0]) cylinder(r=bore-0.5, h=12, center=true);
translate([0, 0, 6]) rotate([-90,0,0]) cylinder(r=bore-0.5, h=4, center=true);
}
}
}
assemble_engine();
-59
View File
@@ -1,59 +0,0 @@
# CADAM Benchmarks
A showcase of what [CADAM](https://adam.new/cadam) builds from a single plain-language
description. Each benchmark starts from the prompt shown and comes out as fully parametric
OpenSCAD — adjustable dimensions and colours, ready to export as `.STL`, `.SCAD`, or `.DXF`.
The `.scad` source for each model is included here, so they double as a record of how well
CADAM turns plain language into real, printable, fully parametric CAD.
### Complex machines & assemblies
| Model | What it shows | Controls |
| --- | --- | --- |
| [V8 engine](13-v8-engine.md) | complete V8 — banks, heads, valve covers, manifold, headers, crank, pistons, oil pan | 22 dims · 8 colors |
| [9-cylinder radial aircraft engine](10-radial-aircraft-engine.md) | nine finned cylinders in a radial star + prop hub | 15 dims · 6 colors |
| [Turbofan jet engine](11-turbofan-jet-engine.md) | full engine — fan, bypass cowl, core stages, exhaust plug | 2 dims · 10 colors |
| [Axial turbine blisk](12-axial-turbine-blisk.md) | ring of twisted aerofoil blades on a keyed hub | 14 dims · 1 color |
### Parametric fundamentals
| Model | What it shows | Controls |
| --- | --- | --- |
| [Twisted hexagonal vase](01-twisted-hex-vase.md) | generative twist-loft, hollow shell with solid floor | 6 dims · 1 color |
| [Knurled control knob](02-knurled-control-knob.md) | diamond knurling, D-bore + set screw, pointer | 15 dims · 2 colors |
| [Hex bolt & nut](03-hex-bolt-and-nut.md) | **real ISO threads** (BOSL2 `screw`/`nut`) | 3 dims · 2 colors |
| [Honeycomb bracket](04-honeycomb-bracket.md) | generative hex lattice, filleted L-bracket | 13 dims · 1 color |
| [NACA 2412 wing](05-naca-airfoil-wing.md) | true airfoil from the NACA equations, tapered loft | 9 dims · 1 color |
| [Threaded jar & lid](06-threaded-jar-and-lid.md) | two **mating** threaded parts | 9 dims · 2 colors |
| [Bevel gear drive](07-bevel-gear-drive.md) | meshing bevel gear pair at 90° | 9 dims · 3 colors |
| [Centrifugal impeller](08-centrifugal-impeller.md) | 7 swept backward-curved blades | 10 dims · 1 color |
| [Planetary gear stage](09-herringbone-planetary-gearbox.md) | full epicyclic assembly, herringbone teeth | 10 dims · 4 colors |
## Regenerating the GIFs
`render.sh` turns any `.scad` into a clean orbiting GIF (and, with `--sheet`, a
4-view contact sheet). It mirrors CADAM's own preview: BOSL2 on the library path,
`color()` parts preserved, a clean orbit around the vertical axis.
Prerequisites (macOS shown; any OpenSCAD ≥ 2021.01 with BOSL2 support works):
```bash
# OpenSCAD CLI + ImageMagick
brew install --cask openscad@snapshot
brew install imagemagick
# BOSL2 (and BOSL) on the OpenSCAD library path — these are bundled in the repo
mkdir -p /tmp/oscad-libs/BOSL2 /tmp/oscad-libs/BOSL
unzip -o ../public/libraries/BOSL2.zip -d /tmp/oscad-libs/BOSL2
unzip -o ../public/libraries/BOSL.zip -d /tmp/oscad-libs/BOSL
```
Then:
```bash
./render.sh 03-hex-bolt-and-nut.scad # -> 03-hex-bolt-and-nut.gif
./render.sh --sheet 09-herringbone-planetary-gearbox.scad # -> *.sheet.png (inspection)
```
Knobs (env vars): `FRAMES` (default 36), `SIZE` (520), `ELEV` (62°), `FPS` (24),
`COLORSCHEME` (Tomorrow), `OPENSCADPATH` (`/tmp/oscad-libs`), `OPENSCAD_BIN`.
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env bash
#
# render.sh — turn a CADAM benchmark .scad into an orbiting GIF (and an optional
# multi-view contact sheet for inspection). Mirrors how CADAM previews models in
# the browser: BOSL2/MCAD on the library path, color() parts preserved, a clean
# orbit around the vertical axis.
#
# Usage:
# ./render.sh model.scad -> model.gif (orbit)
# ./render.sh model.scad out.gif -> out.gif
# ./render.sh --sheet model.scad -> model.sheet.png (iso/front/right/top)
#
# Env knobs (sane defaults):
# FRAMES=36 SIZE=520 ELEV=62 FPS=24 COLORSCHEME=Tomorrow BG=#0d1117
#
set -euo pipefail
# --- locate the OpenSCAD CLI (snapshot/nightly first, then stable) ------------
find_openscad() {
if [[ -n "${OPENSCAD_BIN:-}" && -x "${OPENSCAD_BIN}" ]]; then echo "${OPENSCAD_BIN}"; return; fi
for c in \
"/Applications/OpenSCAD (Nightly).app/Contents/MacOS/OpenSCAD" \
"/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD" \
"$(command -v openscad 2>/dev/null || true)"; do
[[ -n "$c" && -x "$c" ]] && { echo "$c"; return; }
done
echo "ERROR: OpenSCAD CLI not found. Set OPENSCAD_BIN." >&2; exit 1
}
OSCAD="$(find_openscad)"
# BOSL2 / BOSL / MCAD live here (unzipped from public/libraries/*.zip).
export OPENSCADPATH="${OPENSCADPATH:-/tmp/oscad-libs}"
FRAMES="${FRAMES:-36}"
SIZE="${SIZE:-520}"
ELEV="${ELEV:-62}"
FPS="${FPS:-24}"
COLORSCHEME="${COLORSCHEME:-Tomorrow}" # clean near-white backdrop
RENDER_FLAG="${RENDER_FLAG:---render}" # full (manifold) render; --preview is faster but CSG-fuzzy
SHEET=0
if [[ "${1:-}" == "--sheet" ]]; then SHEET=1; shift; fi
SRC="${1:?usage: render.sh [--sheet] model.scad [out.(gif|png)]}"
BASE="$(basename "${SRC%.scad}")"
DIR="$(cd "$(dirname "$SRC")" && pwd)"
OUT="${2:-}"
delay=$(awk "BEGIN{printf \"%.0f\", 100/${FPS}}")
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
render_frame() { # rotx roty rotz file
"$OSCAD" -o "$4" --imgsize="${SIZE},${SIZE}" --projection=perspective \
--colorscheme="$COLORSCHEME" $RENDER_FLAG --viewall --autocenter \
--camera="0,0,0,$1,$2,$3,0" "$SRC" 2>"$tmp/err.log" || {
echo "OpenSCAD failed on $SRC:" >&2; cat "$tmp/err.log" >&2; exit 1; }
}
if [[ "$SHEET" == "1" ]]; then
OUT="${OUT:-$DIR/$BASE.sheet.png}"
render_frame 62 0 25 "$tmp/iso.png"
render_frame 90 0 0 "$tmp/front.png"
render_frame 90 0 90 "$tmp/right.png"
render_frame 0 0 0 "$tmp/top.png"
magick montage "$tmp/iso.png" "$tmp/front.png" "$tmp/right.png" "$tmp/top.png" \
-label '' -tile 2x2 -geometry +6+6 -background white "$OUT"
echo "wrote $OUT"
exit 0
fi
OUT="${OUT:-$DIR/$BASE.gif}"
echo "rendering $FRAMES frames of $BASE ..."
i=0
while [[ $i -lt $FRAMES ]]; do
az=$(awk "BEGIN{printf \"%.2f\", $i*360/$FRAMES}")
printf -v n "%03d" "$i"
render_frame "$ELEV" 0 "$az" "$tmp/f_$n.png"
i=$((i+1))
done
echo "assembling $OUT ..."
magick -delay "$delay" -loop 0 "$tmp"/f_*.png \
-layers OptimizePlus -fuzz 3% -colors 200 "$OUT"
# second pass: strip + max compression
magick "$OUT" -strip -layers Optimize "$OUT"
echo "wrote $OUT ($(du -h "$OUT" | cut -f1))"
-20
View File
@@ -1,20 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
-40
View File
@@ -1,40 +0,0 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
},
},
);
-13701
View File
File diff suppressed because it is too large Load Diff
-144
View File
@@ -1,144 +0,0 @@
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0",
"npm": ">=10"
},
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ignore-pattern 'supabase/**'",
"typecheck": "tsc -b --noEmit",
"format": "prettier --write .",
"preview": "vite preview",
"prepare": "husky"
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.78",
"@ai-sdk/google": "^3.0.75",
"@ai-sdk/react": "^3.0.100",
"@anthropic-ai/sdk": "^0.96.0",
"@fal-ai/client": "^1.10.1",
"@google/genai": "^2.2.0",
"@hookform/resolvers": "^3.9.0",
"@openrouter/ai-sdk-provider": "^2.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@react-three/drei": "^10.0.7",
"@react-three/fiber": "^9.1.2",
"@sentry/react": "^9.1.0",
"@sentry/vite-plugin": "^3.1.2",
"@sitnik/nanoid": "npm:@jsr/sitnik__nanoid@^5.1.2",
"@streamdown/cjk": "^1.0.3",
"@streamdown/code": "^1.1.1",
"@streamdown/math": "^1.0.2",
"@streamdown/mermaid": "^1.0.2",
"@supabase/supabase-js": "^2.108.2",
"@tanstack/react-query": "^5.64.2",
"@tanstack/react-router": "^1.169.2",
"@tanstack/react-start": "^1.167.65",
"@types/omggif": "^1.0.5",
"@types/three": "^0.160.0",
"@zip.js/zip.js": "^2.7.63",
"ai": "^6.0.177",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0",
"fflate": "^0.8.2",
"framer-motion": "^12.16.0",
"image-type": "^6.1.0",
"input-otp": "^1.2.4",
"lottie-react": "^2.4.1",
"lucide-react": "^0.446.0",
"motion": "^12.39.0",
"omggif": "^1.0.10",
"openai": "^6.37.0",
"posthog-js": "^1.307.2",
"react": "19.2.1",
"react-colorful": "^5.6.1",
"react-dom": "19.2.1",
"react-easy-crop": "^5.5.0",
"react-hook-form": "^7.53.0",
"react-resizable-panels": "^2.1.7",
"recharts": "^2.12.7",
"replicate": "^1.0.1",
"sonner": "^1.5.0",
"streamdown": "^2.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"three": "^0.160.1",
"vaul": "^1.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@tanstack/eslint-plugin-query": "^5.64.2",
"@types/node": "^22.7.3",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.20",
"csv-parse": "^6.2.1",
"eslint": "^9.11.1",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"husky": "^9.1.7",
"lint-staged": "^16.1.5",
"nitro": "^3.0.260429-beta",
"postcss": "^8.4.47",
"prettier": "^3.5.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"supabase": "^2.15.8",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"typescript-eslint": "^8.7.0",
"vite": "^8.0.11"
},
"lint-staged": {
"!(supabase/**)*.{js,jsx,ts,tsx}": [
"eslint",
"prettier --write"
],
"supabase/**/*.{js,ts}": [
"deno lint",
"prettier --write"
],
"*.{json,css,scss,md,html,yml,yaml}": [
"prettier --write"
]
}
}
-6
View File
@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

-25
View File
@@ -1,25 +0,0 @@
<svg width="638" height="205" viewBox="0 0 638 205" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M68.7939 190C57.8416 190 47.745 187.347 38.504 182.042C29.4342 176.737 22.2468 169.636 16.9418 160.737C11.6368 151.667 8.98428 141.742 8.98428 130.96C8.98428 120.179 11.6368 110.339 16.9418 101.441C22.2468 92.3709 29.4342 85.1835 38.504 79.8785C47.745 74.5735 57.8416 71.921 68.7939 71.921H127.32V190H68.7939ZM69.0506 162.79C74.6978 162.79 79.9173 161.336 84.7089 158.427C89.5005 155.517 93.2653 151.667 96.0034 146.875C98.9126 141.913 100.367 136.608 100.367 130.96C100.367 125.142 98.9126 119.837 96.0034 115.045C93.2653 110.083 89.5005 106.232 84.7089 103.494C79.9173 100.585 74.6978 99.1305 69.0506 99.1305C63.4033 99.1305 58.1839 100.585 53.3923 103.494C48.6007 106.403 44.7502 110.254 41.8411 115.045C39.103 119.837 37.734 125.142 37.734 130.96C37.734 136.608 39.103 141.913 41.8411 146.875C44.7502 151.667 48.6007 155.517 53.3923 158.427C58.1839 161.336 63.4033 162.79 69.0506 162.79ZM267.884 10.3145V190H208.844C198.234 190 188.394 187.347 179.325 182.042C170.255 176.737 163.067 169.55 157.762 160.48C152.457 151.41 149.805 141.57 149.805 130.96C149.805 120.35 152.457 110.511 157.762 101.441C163.067 92.3709 170.255 85.1835 179.325 79.8785C188.394 74.5735 198.234 71.921 208.844 71.921H240.161V10.3145H267.884ZM209.614 162.79C215.262 162.79 220.481 161.336 225.273 158.427C230.064 155.517 233.829 151.667 236.567 146.875C239.476 141.913 240.931 136.608 240.931 130.96C240.931 125.142 239.476 119.837 236.567 115.045C233.829 110.083 230.064 106.232 225.273 103.494C220.481 100.585 215.262 99.1305 209.614 99.1305C204.138 99.1305 199.004 100.585 194.213 103.494C189.421 106.232 185.656 110.083 182.918 115.045C180.18 119.837 178.811 125.142 178.811 130.96C178.811 136.608 180.18 141.913 182.918 146.875C185.656 151.667 189.421 155.517 194.213 158.427C199.004 161.336 204.138 162.79 209.614 162.79ZM454.556 71.921H573.662C583.245 71.921 591.716 73.8889 599.075 77.8249C606.604 81.5897 612.423 86.9803 616.53 93.9966C620.637 101.013 622.69 109.142 622.69 118.382V190H594.968V118.382C594.968 112.393 592.914 107.345 588.807 103.238C584.7 99.1305 579.652 97.0769 573.662 97.0769C568.015 97.0769 563.052 99.1305 558.774 103.238C554.667 107.345 552.613 112.393 552.613 118.382V190H524.634V118.382C524.634 112.393 522.58 107.345 518.473 103.238C514.366 99.1305 509.403 97.0769 503.585 97.0769C497.766 97.0769 492.718 99.1305 488.44 103.238C484.333 107.345 482.279 112.393 482.279 118.382V190H454.556V71.921Z" fill="#F1F1F1"/>
<g clip-path="url(#clip0_18306_34203)">
<mask id="mask0_18306_34203" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="293" y="70" width="142" height="127">
<path d="M434.756 70H293V196.321H434.756V70Z" fill="white"/>
</mask>
<g mask="url(#mask0_18306_34203)">
<path d="M392.514 178.57L387.141 159.403L383.108 145.108L373.203 109.767L333.123 108.292L324.938 108.014C322.849 108.014 320.892 108.25 319.053 108.668C311.802 110.296 306.389 114.681 302.79 119.038C301.029 121.181 299.729 123.311 298.836 125.106C297.877 127.041 297.075 129.031 296.392 131.064L295.906 132.595C292.083 145.554 293.712 160.336 301.515 172.682C305.929 179.697 312.143 185.279 319.119 189.079C326.948 193.38 335.763 195.412 344.052 194.661C344.118 194.661 344.17 194.661 344.21 194.661L347.941 194.118L394.97 187.367L392.487 178.542L392.514 178.57ZM331.572 123.798C340.794 119.385 352.604 123.506 359.225 133.987C363.455 140.71 364.611 148.532 362.929 155.228C361.879 159.473 359.672 163.273 356.401 166.029C356.02 166.349 355.626 166.669 355.192 166.975C345.721 173.684 332.177 169.815 324.912 158.331C320.156 150.787 319.289 141.823 321.943 134.669C323.349 130.911 325.687 127.654 328.945 125.357C329.799 124.744 330.653 124.243 331.559 123.812H331.572V123.798Z" fill="#004B7A"/>
<path d="M385.632 140.876C384.922 142.365 384.081 143.785 383.122 145.108L373.204 109.767L333.137 108.305C334.963 103.197 338.129 98.7567 342.53 95.6388C355.088 86.7305 373.073 91.8667 382.702 107.108C384.541 110.017 385.947 113.108 386.919 116.239C389.585 124.772 389.06 133.708 385.632 140.89V140.876Z" fill="#004B7A"/>
<path d="M433.541 172.697L394.998 187.396L392.515 178.572L433.541 172.697Z" fill="#004B7A"/>
<path d="M302.789 119.064C301.029 121.208 299.715 123.337 298.835 125.119C297.889 127.054 297.075 129.044 296.392 131.09L305.272 102.041C303.669 107.539 302.842 113.274 302.789 119.064Z" fill="#004B7A"/>
<path d="M405.324 72.2683L397.849 85.3385L363.654 77.8499C353.578 75.1357 343.029 76.5415 334.346 82.7077C330.799 85.1993 327.843 88.3033 325.478 91.8527C324.795 92.8687 324.165 93.8987 323.587 94.9706C321.34 99.1463 319.83 103.795 319.068 108.695L319.041 108.806C318.963 109.335 318.884 109.892 318.831 110.435C318.766 110.908 318.713 111.381 318.674 111.841C318.634 112.244 318.595 112.662 318.582 113.079C318.529 113.678 318.516 114.263 318.503 114.861C318.503 115.321 318.49 115.793 318.503 116.267C318.503 116.698 318.503 117.13 318.529 117.561C318.529 118.048 318.555 118.536 318.582 119.023C318.608 119.482 318.634 119.942 318.687 120.401C318.687 120.665 318.726 120.944 318.752 121.222C318.818 121.821 318.884 122.433 318.963 123.032L302.896 123.282C302.818 121.876 302.778 120.47 302.804 119.065C302.844 113.274 303.685 107.539 305.287 102.041L305.734 100.552C306.654 97.6569 307.783 94.8314 309.123 92.1171C313.196 83.8491 324.401 70.0969 342.885 69.9995L405.324 72.2683Z" fill="#6BC2E7"/>
<path d="M413.652 146.54L387.142 159.401L379.194 163.243C372.127 166.807 364.179 167.628 356.389 165.999C345.131 163.647 334.227 156.158 326.818 144.424C324.834 141.279 323.192 137.98 321.931 134.625C320.473 130.811 319.488 126.886 318.949 123.003L331.442 122.808C331.482 123.128 331.508 123.462 331.547 123.782C332.231 129.086 334.096 134.458 337.183 139.358C343.476 149.324 353.341 154.975 362.918 155.198C367.989 155.309 373.007 153.903 377.342 150.827C379.575 149.241 381.506 147.292 383.096 145.107C384.055 143.784 384.909 142.365 385.605 140.875L413.626 146.54H413.652Z" fill="#6BC2E7"/>
<path d="M413.651 146.542L405.782 116.045L386.918 116.227C389.585 124.759 389.059 133.695 385.63 140.877L413.651 146.542Z" fill="#6BC2E7"/>
<path d="M397.849 85.3398L363.655 77.8513C353.578 75.137 343.03 76.5428 334.346 82.7091C325.873 88.6943 320.684 98.1176 319.055 108.696C318.333 113.317 318.293 118.161 318.963 123.033L331.456 122.838C330.957 117.771 331.535 112.789 333.138 108.32C334.964 103.212 338.129 98.7718 342.53 95.6539C355.089 86.7456 373.073 91.8818 382.703 107.123C383.386 108.209 384.016 109.322 384.568 110.436C385.527 112.343 386.315 114.278 386.92 116.241L405.784 116.059L397.862 85.3537L397.849 85.3398Z" fill="#0087DB"/>
<path d="M433.543 172.696L413.654 146.542L405.785 116.045L397.85 85.3391L405.325 72.269L433.543 172.696Z" fill="#6BC2E7"/>
<path d="M433.543 172.697L392.53 178.571L372.693 181.424C372.693 181.424 372.641 181.424 372.628 181.438L367.662 182.148C367.662 182.148 367.583 182.148 367.544 182.148C367.531 182.148 367.504 182.148 367.491 182.148C367.478 182.148 367.478 182.148 367.452 182.148C367.412 182.148 367.386 182.148 367.36 182.148C347.182 183.804 324.692 172.822 312.593 153.669C306.629 144.232 303.463 133.723 302.885 123.242L318.951 122.991C319.135 124.369 319.384 125.719 319.7 127.111C319.805 127.571 319.91 128.03 320.015 128.461C320.527 130.536 321.171 132.595 321.933 134.614C322.275 135.532 322.642 136.451 323.063 137.356C323.207 137.69 323.352 138.024 323.523 138.358C323.917 139.221 324.35 140.098 324.81 140.975C324.994 141.337 325.191 141.685 325.401 142.033C325.835 142.826 326.321 143.634 326.82 144.413C334.229 156.147 345.132 163.635 356.391 165.988C358.992 166.531 361.606 166.795 364.194 166.781C364.995 166.781 365.783 166.767 366.585 166.698C370.933 166.377 375.202 165.25 379.183 163.232L387.13 159.39L413.64 146.529L433.53 172.683L433.543 172.697Z" fill="#0087DB"/>
</g>
</g>
<defs>
<clipPath id="clip0_18306_34203">
<rect width="141" height="130" fill="white" transform="translate(293 67)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.8 KiB

-17
View File
@@ -1,17 +0,0 @@
<svg width="60" height="55" viewBox="0 0 60 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_4518_8766" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="60" height="55">
<path d="M59.5 0H0.5V55H59.5V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_4518_8766)">
<path d="M42.6871 47.8322L40.4094 39.3882L38.6997 33.0904L34.5005 17.5207L17.5093 16.8707L14.0397 16.748C13.1542 16.748 12.3244 16.8523 11.5448 17.0362C8.47062 17.7536 6.17617 19.6854 4.65024 21.6048C3.90398 22.5492 3.35263 23.4873 2.97394 24.2784C2.5674 25.1307 2.22768 26.0076 1.93809 26.903L1.73204 27.5775C0.111434 33.2866 0.802001 39.7991 4.11003 45.2384C5.98125 48.329 8.61543 50.788 11.5726 52.4621C14.8918 54.357 18.6286 55.2523 22.1428 54.9212C22.1706 54.9212 22.1928 54.9212 22.2096 54.9212L23.7912 54.682L43.7286 51.7079L42.6759 47.8201L42.6871 47.8322ZM16.8521 23.702C20.7616 21.758 25.7682 23.5731 28.5751 28.1907C30.3683 31.1526 30.8584 34.5989 30.1455 37.5486C29.7 39.4189 28.7644 41.093 27.3777 42.3071C27.2162 42.4482 27.0491 42.5892 26.8653 42.7241C22.85 45.6799 17.1084 43.9752 14.0286 38.916C12.0126 35.5924 11.645 31.6432 12.77 28.4913C13.3659 26.8355 14.3571 25.4006 15.7383 24.3888C16.1003 24.1189 16.4622 23.8982 16.8466 23.7081H16.8521V23.702Z" fill="#C12D9C"/>
<path d="M39.7694 31.2254C39.4687 31.8815 39.1122 32.507 38.7056 33.0895L34.501 17.5197L17.5153 16.876C18.2894 14.6254 19.6316 12.6692 21.4971 11.2956C26.8212 7.37095 34.4453 9.63374 38.5274 16.3485C39.307 17.6302 39.903 18.9916 40.3151 20.3714C41.4456 24.1304 41.2229 28.0672 39.7694 31.2315V31.2254Z" fill="#C12D9C"/>
<path d="M60.0798 45.2441L43.7401 51.7199L42.6874 47.8321L60.0798 45.2441Z" fill="#C12D9C"/>
<path d="M4.6501 21.6169C3.90384 22.5612 3.34693 23.4995 2.97381 24.2845C2.57283 25.1369 2.22754 26.0138 1.93796 26.9151L5.70266 14.1172C5.02323 16.5395 4.67237 19.0659 4.6501 21.6169Z" fill="#C12D9C"/>
<path d="M48.1177 0.999554L44.9489 6.75773L30.4525 3.45858C26.1811 2.2628 21.7091 2.88215 18.0278 5.59872C16.5242 6.69639 15.2712 8.06389 14.2687 9.62761C13.9792 10.0753 13.7118 10.529 13.4668 11.0012C12.5145 12.8409 11.874 14.8891 11.551 17.0477L11.5399 17.0966C11.5065 17.3297 11.4731 17.575 11.4508 17.8141C11.4229 18.0226 11.4007 18.2311 11.384 18.4335C11.3673 18.6113 11.3505 18.7954 11.345 18.9793C11.3227 19.2429 11.3171 19.5005 11.3116 19.7642C11.3116 19.9666 11.306 20.175 11.3116 20.3835C11.3116 20.5736 11.3116 20.7637 11.3227 20.9538C11.3227 21.1684 11.3338 21.3832 11.345 21.5977C11.3561 21.8 11.3673 22.0024 11.3895 22.2049C11.3895 22.3213 11.4062 22.4439 11.4174 22.5666C11.4452 22.8302 11.4731 23.1001 11.5065 23.3638L4.6955 23.4741C4.66208 22.8549 4.64538 22.2354 4.65651 21.6161C4.67322 19.0651 5.02964 16.5386 5.70907 14.1164L5.89842 13.4603C6.28825 12.1848 6.76719 10.9399 7.33524 9.74411C9.06166 6.10157 13.8121 0.0429257 21.6479 0L48.1177 0.999554Z" fill="#FC7AB1"/>
<path d="M51.6482 33.721L40.4099 39.3872L37.0405 41.0797C34.0443 42.6496 30.675 43.0113 27.3726 42.2938C22.5999 41.2575 17.9775 37.9583 14.8366 32.7888C13.9957 31.403 13.2995 29.9497 12.7649 28.4717C12.1467 26.7915 11.729 25.0622 11.5007 23.3513L16.7969 23.2656C16.8137 23.4067 16.8247 23.5538 16.8415 23.6948C17.1311 26.0312 17.9218 28.3982 19.2306 30.5568C21.8983 34.9475 26.0806 37.4371 30.1404 37.5353C32.2902 37.5843 34.4175 36.9649 36.2553 35.6098C37.202 34.9107 38.0208 34.0522 38.6946 33.0894C39.1011 32.5068 39.463 31.8814 39.7582 31.2252L51.6371 33.721H51.6482Z" fill="#FC7AB1"/>
<path d="M51.648 33.7229L48.3121 20.2871L40.315 20.3669C41.4455 24.1259 41.2228 28.0628 39.7691 31.227L51.648 33.7229Z" fill="#FC7AB1"/>
<path d="M44.9491 6.75916L30.4528 3.46002C26.1812 2.26423 21.7092 2.88358 18.0281 5.60017C14.436 8.23702 12.2362 12.3885 11.5457 17.049C11.2394 19.085 11.2227 21.219 11.5067 23.3653L16.8029 23.2794C16.5913 21.0472 16.8363 18.852 17.5156 16.8835C18.2898 14.6329 19.6319 12.6768 21.4976 11.3031C26.8217 7.37851 34.4458 9.64131 38.5279 16.3561C38.8175 16.8344 39.0848 17.325 39.3188 17.8155C39.7253 18.6556 40.0593 19.5081 40.3156 20.3728L48.3127 20.293L44.9546 6.76529L44.9491 6.75916Z" fill="#FF2D92"/>
<path d="M60.0805 45.244L51.6489 33.7216L48.3131 20.2859L44.9493 6.75816L48.1183 1L60.0805 45.244Z" fill="#FC7AB1"/>
<path d="M60.0807 45.2439L42.694 47.8317L34.2847 49.0888C34.2847 49.0888 34.2624 49.0888 34.2569 49.0951L32.1517 49.4078C32.1517 49.4078 32.1183 49.4078 32.1015 49.4078C32.096 49.4078 32.0849 49.4078 32.0792 49.4078C32.0737 49.4078 32.0737 49.4078 32.0626 49.4078C32.0459 49.4078 32.0348 49.4078 32.0236 49.4078C23.4695 50.1374 13.9352 45.2991 8.8061 36.8613C6.27773 32.7036 4.93557 28.0737 4.69054 23.4562L11.5015 23.3457C11.5795 23.9528 11.6853 24.5477 11.819 25.1609C11.8635 25.3633 11.9081 25.5656 11.9526 25.7557C12.1698 26.6695 12.4427 27.5769 12.7657 28.4661C12.9105 28.8708 13.0664 29.2757 13.2447 29.6742C13.3059 29.8214 13.3672 29.9685 13.4396 30.1158C13.6066 30.4959 13.7904 30.8823 13.9853 31.2687C14.0633 31.4281 14.1468 31.5814 14.2359 31.7346C14.4197 32.0841 14.6258 32.4399 14.8374 32.7832C17.9784 37.9527 22.6006 41.2519 27.3735 42.2882C28.4761 42.5274 29.5843 42.644 30.6814 42.6377C31.0212 42.6377 31.3553 42.6317 31.695 42.601C33.5384 42.4599 35.3483 41.9633 37.0357 41.0741L40.405 39.3816L51.6435 33.7154L60.0751 45.2378L60.0807 45.2439Z" fill="#FF2D92"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

-17
View File
@@ -1,17 +0,0 @@
<svg width="1592" height="1419" viewBox="0 0 1592 1419" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_18306_34189" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="1592" height="1419">
<path d="M1592 0.524414H0.209473V1419H1592V0.524414Z" fill="white"/>
</mask>
<g mask="url(#mask0_18306_34189)">
<path d="M1117.66 1219.67L1057.33 1004.45L1012.04 843.928L900.815 447.082L450.752 430.515L358.849 427.389C335.394 427.389 313.415 430.046 292.763 434.733C211.335 453.02 150.56 502.256 110.141 551.179C90.3739 575.249 75.77 599.16 65.7392 619.326C54.9706 641.048 45.9722 663.399 38.3017 686.222L32.8436 703.412C-10.0829 848.929 8.20887 1014.92 95.8319 1153.56C145.397 1232.33 215.171 1295.01 293.501 1337.68C381.419 1385.98 480.399 1408.8 573.483 1400.36C574.221 1400.36 574.807 1400.36 575.252 1400.36L617.145 1394.26L1145.25 1318.45L1117.36 1219.36L1117.66 1219.67ZM433.343 604.634C536.898 555.083 669.513 601.349 743.861 719.042C791.361 794.537 804.341 882.377 785.457 957.558C773.659 1005.23 748.876 1047.9 712.144 1078.85C707.867 1082.44 703.442 1086.04 698.574 1089.48C592.216 1164.81 440.131 1121.36 358.554 992.412C305.154 907.698 295.418 807.041 325.216 726.704C341 684.502 367.257 647.928 403.841 622.138C413.429 615.258 423.016 609.634 433.197 604.788H433.343V604.634Z" fill="#002B7B"/>
<path d="M1040.38 796.404C1032.41 813.126 1022.97 829.069 1012.2 843.917L900.827 447.07L450.911 430.662C471.415 373.298 506.966 323.438 556.38 288.427C697.404 188.395 899.352 246.069 1007.48 417.218C1028.13 449.886 1043.92 484.585 1054.83 519.753C1084.78 615.565 1078.88 715.908 1040.38 796.559V796.404Z" fill="#002B7B"/>
<path d="M1578.36 1153.72L1145.55 1318.78L1117.67 1219.69L1578.36 1153.72Z" fill="#002B7B"/>
<path d="M110.138 551.476C90.3706 575.546 75.6192 599.461 65.7359 619.468C55.1148 641.195 45.9689 663.545 38.2983 686.519L138.018 360.321C120.021 422.062 110.727 486.455 110.138 551.476Z" fill="#002B7B"/>
<path d="M1261.5 25.9984L1177.57 172.765L793.589 88.6749C680.448 58.1964 561.995 73.9826 464.486 143.223C424.658 171.201 391.468 206.056 364.916 245.913C357.245 257.323 350.164 268.889 343.674 280.924C318.449 327.814 301.485 380.018 292.929 435.038L292.634 436.285C291.749 442.227 290.864 448.479 290.274 454.572C289.536 459.886 288.946 465.201 288.503 470.361C288.061 474.893 287.618 479.584 287.471 484.27C286.881 490.991 286.733 497.557 286.586 504.277C286.586 509.437 286.438 514.747 286.586 520.062C286.586 524.908 286.586 529.753 286.881 534.599C286.881 540.068 287.176 545.542 287.471 551.011C287.766 556.167 288.061 561.326 288.651 566.486C288.651 569.453 289.093 572.579 289.388 575.705C290.126 582.425 290.864 589.305 291.749 596.026L111.34 598.838C110.454 583.053 110.012 567.264 110.307 551.48C110.749 486.459 120.19 422.061 138.187 360.325L143.203 343.601C153.528 311.09 166.214 279.361 181.261 248.882C226.99 156.04 352.819 1.61559 560.373 0.521484L1261.5 25.9984Z" fill="#6BC2E7"/>
<path d="M1355.02 860.007L1057.34 1004.43L968.094 1047.57C888.731 1087.58 799.484 1096.8 712.01 1078.51C585.591 1052.1 463.153 968.008 379.956 836.246C357.682 800.923 339.242 763.881 325.081 726.211C308.707 683.385 297.644 639.308 291.596 595.7L431.881 593.516C432.325 597.11 432.618 600.86 433.062 604.454C440.734 664.006 461.678 724.336 496.345 779.356C567.005 891.266 677.788 954.722 785.323 957.224C842.266 958.475 898.615 942.687 947.296 908.147C972.372 890.328 994.058 868.447 1011.91 843.908C1022.67 829.057 1032.26 813.118 1040.08 796.391L1354.73 860.007H1355.02Z" fill="#6BC2E7"/>
<path d="M1355.02 860.027L1266.65 517.572L1054.83 519.606C1084.77 615.418 1078.87 715.761 1040.37 796.412L1355.02 860.027Z" fill="#6BC2E7"/>
<path d="M1177.58 172.776L793.598 88.6865C680.453 58.208 561.999 73.9942 464.495 143.235C369.347 210.444 311.079 316.26 292.787 435.046C284.674 486.94 284.231 541.332 291.755 596.038L432.041 593.849C426.435 536.955 432.925 481.002 450.92 430.828C471.425 373.465 506.975 323.606 556.394 288.595C697.418 188.562 899.365 246.237 1007.49 417.386C1015.16 429.577 1022.24 442.081 1028.44 454.584C1039.21 475.997 1048.06 497.724 1054.84 519.764L1266.67 517.731L1177.72 172.932L1177.58 172.776Z" fill="#0171B7"/>
<path d="M1578.38 1153.71L1355.04 860.023L1266.68 517.569L1177.58 172.771L1261.52 26.0049L1578.38 1153.71Z" fill="#6BC2E7"/>
<path d="M1578.38 1153.71L1117.84 1219.67L895.096 1251.71C895.096 1251.71 894.505 1251.71 894.358 1251.87L838.597 1259.84C838.597 1259.84 837.713 1259.84 837.268 1259.84C837.122 1259.84 836.829 1259.84 836.678 1259.84C836.531 1259.84 836.531 1259.84 836.238 1259.84C835.794 1259.84 835.5 1259.84 835.203 1259.84C608.623 1278.44 356.079 1155.12 220.219 940.054C153.248 834.082 117.697 716.075 111.207 598.382L291.616 595.565C293.681 611.04 296.484 626.201 300.024 641.831C301.204 646.991 302.384 652.146 303.565 656.992C309.318 680.284 316.546 703.412 325.101 726.076C328.937 736.391 333.067 746.711 337.788 756.867C339.41 760.62 341.033 764.37 342.951 768.124C347.376 777.811 352.244 787.662 357.407 797.508C359.472 801.571 361.685 805.48 364.045 809.384C368.913 818.293 374.371 827.361 379.977 836.111C463.175 967.873 585.609 1051.96 712.032 1078.38C741.239 1084.47 770.592 1087.45 799.653 1087.29C808.652 1087.29 817.501 1087.13 826.501 1086.35C875.328 1082.75 923.268 1070.1 967.965 1047.43L1057.21 1004.29L1354.9 859.872L1578.23 1153.56L1578.38 1153.71Z" fill="#0171B7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

-19
View File
@@ -1,19 +0,0 @@
<svg width="161" height="161" viewBox="396.4 51 161 161" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_553_62)">
<path d="M507.543 178.503L502.107 159.48L498.027 145.293L488.006 110.217L447.458 108.753L439.178 108.477C437.065 108.477 435.084 108.711 433.224 109.126C425.887 110.742 420.412 115.094 416.77 119.418C414.989 121.545 413.674 123.659 412.77 125.441C411.8 127.361 410.989 129.337 410.298 131.354L409.806 132.873C405.939 145.735 407.587 160.406 415.481 172.66C419.947 179.622 426.233 185.162 433.29 188.933C441.211 193.202 450.129 195.219 458.515 194.473C458.582 194.473 458.635 194.473 458.675 194.473L462.449 193.934L510.028 187.234L507.516 178.476L507.543 178.503ZM445.889 124.142C455.219 119.763 467.167 123.852 473.865 134.255C478.145 140.927 479.314 148.691 477.613 155.336C476.55 159.549 474.317 163.321 471.008 166.056C470.623 166.374 470.224 166.692 469.785 166.996C460.203 173.654 446.501 169.814 439.151 158.417C434.34 150.929 433.463 142.032 436.148 134.932C437.57 131.202 439.935 127.969 443.231 125.69C444.095 125.082 444.959 124.584 445.876 124.156H445.889V124.142Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M500.579 141.09C499.861 142.568 499.01 143.977 498.04 145.29L488.006 110.214L447.471 108.764C449.318 103.694 452.521 99.2869 456.973 96.1924C469.679 87.351 487.873 92.4486 497.615 107.576C499.476 110.463 500.898 113.53 501.881 116.638C504.579 125.107 504.047 133.976 500.579 141.104V141.09Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M549.049 172.675L510.055 187.263L507.543 178.504L549.049 172.675Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M416.769 119.444C414.988 121.572 413.659 123.686 412.769 125.454C411.812 127.374 410.988 129.35 410.297 131.38L419.281 102.549C417.66 108.006 416.822 113.698 416.769 119.444Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M520.499 72.9962L512.937 85.9682L478.342 78.5359C468.149 75.842 457.477 77.2373 448.692 83.3572C445.103 85.83 442.113 88.9107 439.721 92.4335C439.03 93.4419 438.392 94.4642 437.807 95.528C435.534 99.6724 434.006 104.286 433.235 109.149L433.208 109.26C433.129 109.785 433.049 110.337 432.996 110.876C432.929 111.346 432.876 111.815 432.836 112.271C432.796 112.672 432.757 113.086 432.743 113.501C432.69 114.095 432.677 114.675 432.664 115.269C432.664 115.725 432.65 116.195 432.664 116.664C432.664 117.093 432.664 117.521 432.69 117.949C432.69 118.433 432.717 118.916 432.743 119.4C432.77 119.856 432.796 120.312 432.85 120.767C432.85 121.03 432.889 121.306 432.916 121.582C432.983 122.177 433.049 122.784 433.129 123.378L416.875 123.627C416.795 122.232 416.755 120.836 416.782 119.441C416.822 113.694 417.672 108.003 419.294 102.546L419.745 101.068C420.676 98.1942 421.819 95.3898 423.174 92.6959C427.294 84.49 438.631 70.8411 457.33 70.7444L520.499 72.9962Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M528.927 146.709L502.107 159.474L494.066 163.287C486.916 166.823 478.876 167.638 470.995 166.022C459.605 163.687 448.574 156.255 441.078 144.609C439.071 141.487 437.41 138.213 436.134 134.884C434.659 131.098 433.662 127.203 433.117 123.348L445.756 123.155C445.796 123.473 445.823 123.804 445.863 124.122C446.554 129.385 448.441 134.718 451.564 139.581C457.93 149.472 467.911 155.081 477.6 155.302C482.73 155.412 487.807 154.017 492.193 150.964C494.452 149.389 496.406 147.455 498.014 145.286C498.984 143.974 499.848 142.565 500.552 141.087L528.9 146.709H528.927Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M528.928 146.714L520.967 116.446L501.883 116.625C504.58 125.094 504.049 133.963 500.58 141.091L528.928 146.714Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M512.942 85.9682L478.347 78.5359C468.153 75.8421 457.481 77.2374 448.696 83.3573C440.124 89.2976 434.874 98.6501 433.226 109.149C432.495 113.736 432.456 118.543 433.133 123.378L445.772 123.185C445.267 118.156 445.852 113.211 447.474 108.776C449.321 103.706 452.524 99.2994 456.976 96.2049C469.682 87.3635 487.876 92.4611 497.618 107.588C498.309 108.666 498.947 109.771 499.505 110.876C500.475 112.769 501.273 114.689 501.884 116.637L520.969 116.457L512.955 85.9821L512.942 85.9682Z" fill="#3A85D5" style="fill:#3A85D5;fill:color(display-p3 0.2275 0.5216 0.8353);fill-opacity:1;"/>
<path d="M549.053 172.672L528.932 146.714L520.971 116.446L512.943 85.9708L520.506 72.9988L549.053 172.672Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M549.048 172.671L507.556 178.501L487.488 181.333C487.488 181.333 487.435 181.333 487.421 181.347L482.398 182.051C482.398 182.051 482.318 182.051 482.278 182.051C482.265 182.051 482.238 182.051 482.225 182.051C482.212 182.051 482.212 182.051 482.185 182.051C482.145 182.051 482.119 182.051 482.092 182.051C461.678 183.695 438.925 172.795 426.685 153.786C420.651 144.42 417.448 133.99 416.863 123.587L433.117 123.339C433.303 124.706 433.556 126.046 433.875 127.428C433.981 127.884 434.087 128.34 434.194 128.768C434.712 130.826 435.363 132.871 436.134 134.874C436.48 135.786 436.852 136.697 437.277 137.595C437.423 137.927 437.57 138.258 437.742 138.59C438.141 139.447 438.58 140.317 439.045 141.187C439.231 141.546 439.43 141.892 439.643 142.237C440.081 143.025 440.573 143.826 441.078 144.599C448.574 156.245 459.605 163.678 470.995 166.012C473.626 166.551 476.271 166.813 478.889 166.8C479.7 166.8 480.497 166.786 481.308 166.717C485.707 166.399 490.026 165.28 494.053 163.277L502.094 159.464L528.914 146.699L549.035 172.657L549.048 172.671Z" fill="#3A85D5" style="fill:#3A85D5;fill:color(display-p3 0.2275 0.5216 0.8353);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_553_62">
<rect width="141" height="131" fill="white" style="fill:white;fill-opacity:1;" transform="translate(406.4 66)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 MiB

-20
View File
@@ -1,20 +0,0 @@
<svg width="752" height="205" viewBox="0 0 752 205" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M71.4392 192.567C59.9735 192.567 49.4491 189.829 39.8659 184.353C30.2826 178.877 22.6674 171.432 17.0201 162.02C11.544 152.437 8.80595 142.084 8.80595 130.96C8.80595 119.666 11.544 109.313 17.0201 99.9006C22.6674 90.4885 30.1971 83.0443 39.6092 77.5682C49.1924 72.0921 59.7168 69.354 71.1825 69.354C79.3967 69.354 87.2686 70.8942 94.7983 73.9745C102.328 77.0548 109.002 81.5042 114.82 87.3226L95.8251 106.831C92.5736 103.409 88.8088 100.842 84.5306 99.1305C80.4235 97.4192 75.9741 96.5635 71.1825 96.5635C65.193 96.5635 59.5457 98.1037 54.2407 101.184C49.1068 104.264 45.0853 108.457 42.1761 113.762C39.2669 118.896 37.8123 124.629 37.8123 130.96C37.8123 137.121 39.2669 142.854 42.1761 148.159C45.2564 153.464 49.3635 157.657 54.4974 160.737C59.6313 163.817 65.2785 165.357 71.4392 165.357C76.2308 165.357 80.6802 164.502 84.7873 162.79C89.0655 160.908 92.7448 158.341 95.8251 155.09L114.82 174.598C109.002 180.246 102.328 184.695 94.7983 187.946C87.2686 191.027 79.4823 192.567 71.4392 192.567ZM183.372 190C172.419 190 162.323 187.347 153.082 182.042C144.012 176.737 136.825 169.636 131.52 160.737C126.215 151.667 123.562 141.742 123.562 130.96C123.562 120.179 126.215 110.339 131.52 101.441C136.825 92.3709 144.012 85.1835 153.082 79.8785C162.323 74.5735 172.419 71.921 183.372 71.921H241.898V190H183.372ZM183.628 162.79C189.276 162.79 194.495 161.336 199.287 158.427C204.078 155.517 207.843 151.667 210.581 146.875C213.49 141.913 214.945 136.608 214.945 130.96C214.945 125.142 213.49 119.837 210.581 115.045C207.843 110.083 204.078 106.232 199.287 103.494C194.495 100.585 189.276 99.1305 183.628 99.1305C177.981 99.1305 172.762 100.585 167.97 103.494C163.178 106.403 159.328 110.254 156.419 115.045C153.681 119.837 152.312 125.142 152.312 130.96C152.312 136.608 153.681 141.913 156.419 146.875C159.328 151.667 163.178 155.517 167.97 158.427C172.762 161.336 177.981 162.79 183.628 162.79ZM382.462 10.3145V190H323.422C312.812 190 302.972 187.347 293.902 182.042C284.832 176.737 277.645 169.55 272.34 160.48C267.035 151.41 264.382 141.57 264.382 130.96C264.382 120.35 267.035 110.511 272.34 101.441C277.645 92.3709 284.832 85.1835 293.902 79.8785C302.972 74.5735 312.812 71.921 323.422 71.921H354.739V10.3145H382.462ZM324.192 162.79C329.839 162.79 335.059 161.336 339.85 158.427C344.642 155.517 348.407 151.667 351.145 146.875C354.054 141.913 355.509 136.608 355.509 130.96C355.509 125.142 354.054 119.837 351.145 115.045C348.407 110.083 344.642 106.232 339.85 103.494C335.059 100.585 329.839 99.1305 324.192 99.1305C318.716 99.1305 313.582 100.585 308.79 103.494C303.999 106.232 300.234 110.083 297.496 115.045C294.758 119.837 293.389 125.142 293.389 130.96C293.389 136.608 294.758 141.913 297.496 146.875C300.234 151.667 303.999 155.517 308.79 158.427C313.582 161.336 318.716 162.79 324.192 162.79ZM569.134 71.921H688.24C697.823 71.921 706.294 73.8889 713.652 77.8249C721.182 81.5897 727 86.9803 731.108 93.9966C735.215 101.013 737.268 109.142 737.268 118.382V190H709.545V118.382C709.545 112.393 707.492 107.345 703.385 103.238C699.278 99.1305 694.229 97.0769 688.24 97.0769C682.592 97.0769 677.63 99.1305 673.352 103.238C669.244 107.345 667.191 112.393 667.191 118.382V190H639.211V118.382C639.211 112.393 637.158 107.345 633.051 103.238C628.944 99.1305 623.981 97.0769 618.162 97.0769C612.344 97.0769 607.296 99.1305 603.017 103.238C598.91 107.345 596.857 112.393 596.857 118.382V190H569.134V71.921Z" fill="white" style="fill:white;fill-opacity:1;"/>
<g clip-path="url(#clip0_553_62)">
<path d="M507.543 178.503L502.107 159.48L498.027 145.293L488.006 110.217L447.458 108.753L439.178 108.477C437.065 108.477 435.084 108.711 433.224 109.126C425.887 110.742 420.412 115.094 416.77 119.418C414.989 121.545 413.674 123.659 412.77 125.441C411.8 127.361 410.989 129.337 410.298 131.354L409.806 132.873C405.939 145.735 407.587 160.406 415.481 172.66C419.947 179.622 426.233 185.162 433.29 188.933C441.211 193.202 450.129 195.219 458.515 194.473C458.582 194.473 458.635 194.473 458.675 194.473L462.449 193.934L510.028 187.234L507.516 178.476L507.543 178.503ZM445.889 124.142C455.219 119.763 467.167 123.852 473.865 134.255C478.145 140.927 479.314 148.691 477.613 155.336C476.55 159.549 474.317 163.321 471.008 166.056C470.623 166.374 470.224 166.692 469.785 166.996C460.203 173.654 446.501 169.814 439.151 158.417C434.34 150.929 433.463 142.032 436.148 134.932C437.57 131.202 439.935 127.969 443.231 125.69C444.095 125.082 444.959 124.584 445.876 124.156H445.889V124.142Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M500.579 141.09C499.861 142.568 499.01 143.977 498.04 145.29L488.006 110.214L447.471 108.764C449.318 103.694 452.521 99.2869 456.973 96.1924C469.679 87.351 487.873 92.4486 497.615 107.576C499.476 110.463 500.898 113.53 501.881 116.638C504.579 125.107 504.047 133.976 500.579 141.104V141.09Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M549.049 172.675L510.055 187.263L507.543 178.504L549.049 172.675Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M416.769 119.444C414.988 121.572 413.659 123.686 412.769 125.454C411.812 127.374 410.988 129.35 410.297 131.38L419.281 102.549C417.66 108.006 416.822 113.698 416.769 119.444Z" fill="#1D4A76" style="fill:#1D4A76;fill:color(display-p3 0.1137 0.2902 0.4627);fill-opacity:1;"/>
<path d="M520.499 72.9962L512.937 85.9682L478.342 78.5359C468.149 75.842 457.477 77.2373 448.692 83.3572C445.103 85.83 442.113 88.9107 439.721 92.4335C439.03 93.4419 438.392 94.4642 437.807 95.528C435.534 99.6724 434.006 104.286 433.235 109.149L433.208 109.26C433.129 109.785 433.049 110.337 432.996 110.876C432.929 111.346 432.876 111.815 432.836 112.271C432.796 112.672 432.757 113.086 432.743 113.501C432.69 114.095 432.677 114.675 432.664 115.269C432.664 115.725 432.65 116.195 432.664 116.664C432.664 117.093 432.664 117.521 432.69 117.949C432.69 118.433 432.717 118.916 432.743 119.4C432.77 119.856 432.796 120.312 432.85 120.767C432.85 121.03 432.889 121.306 432.916 121.582C432.983 122.177 433.049 122.784 433.129 123.378L416.875 123.627C416.795 122.232 416.755 120.836 416.782 119.441C416.822 113.694 417.672 108.003 419.294 102.546L419.745 101.068C420.676 98.1942 421.819 95.3898 423.174 92.6959C427.294 84.49 438.631 70.8411 457.33 70.7444L520.499 72.9962Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M528.927 146.709L502.107 159.474L494.066 163.287C486.916 166.823 478.876 167.638 470.995 166.022C459.605 163.687 448.574 156.255 441.078 144.609C439.071 141.487 437.41 138.213 436.134 134.884C434.659 131.098 433.662 127.203 433.117 123.348L445.756 123.155C445.796 123.473 445.823 123.804 445.863 124.122C446.554 129.385 448.441 134.718 451.564 139.581C457.93 149.472 467.911 155.081 477.6 155.302C482.73 155.412 487.807 154.017 492.193 150.964C494.452 149.389 496.406 147.455 498.014 145.286C498.984 143.974 499.848 142.565 500.552 141.087L528.9 146.709H528.927Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M528.928 146.714L520.967 116.446L501.883 116.625C504.58 125.094 504.049 133.963 500.58 141.091L528.928 146.714Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M512.942 85.9682L478.347 78.5359C468.153 75.8421 457.481 77.2374 448.696 83.3573C440.124 89.2976 434.874 98.6501 433.226 109.149C432.495 113.736 432.456 118.543 433.133 123.378L445.772 123.185C445.267 118.156 445.852 113.211 447.474 108.776C449.321 103.706 452.524 99.2994 456.976 96.2049C469.682 87.3635 487.876 92.4611 497.618 107.588C498.309 108.666 498.947 109.771 499.505 110.876C500.475 112.769 501.273 114.689 501.884 116.637L520.969 116.457L512.955 85.9821L512.942 85.9682Z" fill="#3A85D5" style="fill:#3A85D5;fill:color(display-p3 0.2275 0.5216 0.8353);fill-opacity:1;"/>
<path d="M549.053 172.672L528.932 146.714L520.971 116.446L512.943 85.9708L520.506 72.9988L549.053 172.672Z" fill="#80C0E3" style="fill:#80C0E3;fill:color(display-p3 0.5020 0.7529 0.8902);fill-opacity:1;"/>
<path d="M549.048 172.671L507.556 178.501L487.488 181.333C487.488 181.333 487.435 181.333 487.421 181.347L482.398 182.051C482.398 182.051 482.318 182.051 482.278 182.051C482.265 182.051 482.238 182.051 482.225 182.051C482.212 182.051 482.212 182.051 482.185 182.051C482.145 182.051 482.119 182.051 482.092 182.051C461.678 183.695 438.925 172.795 426.685 153.786C420.651 144.42 417.448 133.99 416.863 123.587L433.117 123.339C433.303 124.706 433.556 126.046 433.875 127.428C433.981 127.884 434.087 128.34 434.194 128.768C434.712 130.826 435.363 132.871 436.134 134.874C436.48 135.786 436.852 136.697 437.277 137.595C437.423 137.927 437.57 138.258 437.742 138.59C438.141 139.447 438.58 140.317 439.045 141.187C439.231 141.546 439.43 141.892 439.643 142.237C440.081 143.025 440.573 143.826 441.078 144.599C448.574 156.245 459.605 163.678 470.995 166.012C473.626 166.551 476.271 166.813 478.889 166.8C479.7 166.8 480.497 166.786 481.308 166.717C485.707 166.399 490.026 165.28 494.053 163.277L502.094 159.464L528.914 146.699L549.035 172.657L549.048 172.671Z" fill="#3A85D5" style="fill:#3A85D5;fill:color(display-p3 0.2275 0.5216 0.8353);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_553_62">
<rect width="141" height="131" fill="white" style="fill:white;fill-opacity:1;" transform="translate(406.4 66)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="Autodesk Fusion 360">
<path fill="#ff9548" d="M46.79097-.00247339 7.8151404 23.849982l.2080078.140625 7.6506218-.679759L45.733663 5.9498164Z"/>
<path fill="#ff9548" d="m46.398594.36621094.381406-.36621094c-.09 59.94-.13 119.88.02 179.82-6.626401 3.99384-34.558467 20.8215-38.97 23.43-.01-59.8.01-119.6-.01-179.4C16.708712 15.663236 38.795821 8.8363876 46.398594.36621094Z"/>
<path fill="#ff6b00" d="M46.78 0h171.61c6.13 1.02 11.96 5.71 12.23 12.27.19 9.33.01 18.66.07 28 .01 46.5-.01 93.01.01 139.51-61.3.1-122.6.02-183.9.04C46.65 119.88 46.69 59.94 46.78 0m64.99 40.53c.03 33.12 0 66.25.02 99.37 8.3-.02 16.6.05 24.9-.04-.06-13.36-.02-26.73-.03-40.1 10.87.02 21.74-.07 32.62.04.1-5.89.16-11.8-.04-17.69-10.86.12-21.71.03-32.57.05-.06-7.88.07-15.76-.08-23.65 12.91-.24 25.83-.03 38.74-.1-.01-5.96-.01-11.93 0-17.89-21.18-.01-42.37-.02-63.56.01Z"/>
<path fill="#933c00" d="M230.69 40.27c5.94.06 11.87-.15 17.81.13-.24 71.86-.04 143.73-.1 215.6H40.9c-5.81-1.25-11.45-5.68-11.72-12.02-.18-17.83-.03-35.67-.07-53.51 5.9-3.54 11.8-7.1 17.69-10.65 61.3-.02 122.6.06 183.9-.04-.02-46.5 0-93.01-.01-139.51m-67.65 158.79c-5.98 3.18-6.67 13.1-.89 16.77 4.26 2.55 9.33 3.16 13.81 5.21 2.8 1.01 3.04 5.21.95 7.02-4.15 2.47-11.05.88-11.18-4.73-2.9 0-5.79.01-8.69 0 .21 3.27 1.31 6.63 3.86 8.84 4.07 3.74 10.03 4.07 15.26 3.52 4.91-.51 10.17-3.53 11.22-8.7.56-3.71.37-8.19-2.82-10.71-4.38-3.61-10.41-3.8-15.3-6.43-2.19-1.02-2.31-4.58-.27-5.79 3.7-2.14 9.43-.51 9.84 4.26 2.86.02 5.73.02 8.59 0-.24-4.26-2.65-8.43-6.73-10.04-5.58-2.23-12.33-2.14-17.65.78m-70.92-1.44c.17 12.52.04 25.05.07 37.57 3.08 0 6.16 0 9.24.09.34-5.14.03-10.28.17-15.42 4.1.01 8.2.01 12.3 0 0-2.21 0-4.41-.01-6.61-4.11.01-8.21.01-12.32.01.03-2.93.02-5.87-.08-8.79 4.91-.27 9.83-.06 14.75-.13 0-2.27 0-4.54.01-6.8-8.05.04-16.09-.12-24.13.08m28.92-.04c.34 9.78-.47 19.63.45 29.38 1.76 9.44 14.87 11.74 21.22 5.52.26.9.53 1.8.82 2.7 2.63.01 5.26.01 7.89.12.42-12.58.14-25.2.13-37.79-3.13.03-6.26.02-9.39.11.16 8.8 0 17.6.09 26.41.83 6.26-11.34 7.35-11.7.85-.28-9.1-.05-18.22-.11-27.32-3.13-.03-6.26-.03-9.4.02Z"/>
<path fill="#fff" d="M111.77 40.53c21.19-.03 42.38-.02 63.56-.01-.01 5.96-.01 11.93 0 17.89-12.91.07-25.83-.14-38.74.1.15 7.89.02 15.77.08 23.65 10.86-.02 21.71.07 32.57-.05.2 5.89.14 11.8.04 17.69-10.88-.11-21.75-.02-32.62-.04.01 13.37-.03 26.74.03 40.1-8.3.09-16.6.02-24.9.04-.02-33.12.01-66.25-.02-99.37ZM163.04 199.06c5.32-2.92 12.07-3.01 17.65-.78 4.08 1.61 6.49 5.78 6.73 10.04-2.86.02-5.73.02-8.59 0-.41-4.77-6.14-6.4-9.84-4.26-2.04 1.21-1.92 4.77.27 5.79 4.89 2.63 10.92 2.82 15.3 6.43 3.19 2.52 3.38 7 2.82 10.71-1.05 5.17-6.31 8.19-11.22 8.7-5.23.55-11.19.22-15.26-3.52-2.55-2.21-3.65-5.57-3.86-8.84 2.9.01 5.79 0 8.69 0 .13 5.61 7.03 7.2 11.18 4.73 2.09-1.81 1.85-6.01-.95-7.02-4.48-2.05-9.55-2.66-13.81-5.21-5.78-3.67-5.09-13.59.89-16.77ZM92.12 197.62c8.04-.2 16.08-.04 24.13-.08-.01 2.26-.01 4.53-.01 6.8-4.92.07-9.84-.14-14.75.13.1 2.92.11 5.86.08 8.79 4.11 0 8.21 0 12.32-.01.01 2.2.01 4.4.01 6.61-4.1.01-8.2.01-12.3 0-.14 5.14.17 10.28-.17 15.42-3.08-.09-6.16-.09-9.24-.09-.03-12.52.1-25.05-.07-37.57ZM121.04 197.58c3.14-.05 6.27-.05 9.4-.02.06 9.1-.17 18.22.11 27.32.36 6.5 12.53 5.41 11.7-.85-.09-8.81.07-17.61-.09-26.41 3.13-.09 6.26-.08 9.39-.11.01 12.59.29 25.21-.13 37.79-2.63-.11-5.26-.11-7.89-.12-.29-.9-.56-1.8-.82-2.7-6.35 6.22-19.46 3.92-21.22-5.52-.92-9.75-.11-19.6-.45-29.38Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -0.5 32 32" role="img" aria-label="SolidWorks"><g transform="translate(-346.74 -463.32)">
<g transform="matrix(1.8601 0 0 -1.8601 -574.67 1808.3)">
<path d="m552.71 714.79c0.002 0.069-0.029 0.157-0.089 0.204-0.061 0.049-0.127 0.059-0.192 0.059h-0.333l-2.57-5.824-0.67 3.12c-0.039 0.274-0.259 0.461-0.519 0.446h-0.432l-2.029-3.532 0.02 5.51c0.002 0.074-0.027 0.164-0.092 0.215-0.063 0.052-0.134 0.065-0.205 0.065h-0.433v-6.62h0.552v0.001c0.003 0 0.008-0.001 0.012-0.001 0.162-0.002 0.309 0.039 0.429 0.126 0.109 0.078 0.203 0.187 0.286 0.318l1.776 3.169 0.74-3.613h0.46 0.014c0.302 0 0.555 0.176 0.687 0.476l2.554 5.743c0.018 0.047 0.033 0.078 0.034 0.138m23.281 0.263h-2.732c-1.276-0.003-1.411-1.051-1.417-1.545 0-0.52 0.378-0.934 0.566-1.102l1.86-1.822c0.221-0.217 0.328-0.473 0.328-0.711 0-0.403-0.308-0.733-0.806-0.731-0.001-0.003-2.408-0.004-2.43-0.01-0.079-0.015-0.162-0.118-0.16-0.201h-0.007l0.007-0.003v-0.497h2.704c1.277 0.005 1.411 1.051 1.418 1.545 0 0.002-0.001 0.002-0.001 0.004 0.001 0.684-0.629 1.213-0.633 1.215l-1.764 1.736c-0.22 0.217-0.356 0.44-0.357 0.68 0.001 0.405 0.309 0.733 0.805 0.733 0.003 0.001 2.438 0.003 2.458 0.009l0.006-0.018-0.005 0.018c0.079 0.015 0.163 0.119 0.161 0.201 0 0-0.001 0.001 0 0.002 0 0.004-0.001 0.403-0.001 0.497m-19.498-0.664c-1.214 0.067-2.602-0.648-3.079-2.578-0.47-1.892 0.408-2.811 1.598-2.964l0.124-0.006c1.228-0.065 2.535 0.651 2.995 2.581 0.449 1.893-0.431 2.844-1.638 2.967m2.348-3.024c-0.447-1.882-1.803-3.367-3.968-3.2v0.001c-2.014 0.233-2.591 2.005-2.166 3.703 0.469 1.88 1.82 3.365 3.954 3.2v-0.002c1.987-0.232 2.582-2.006 2.18-3.702m12.055 3.625c-0.056 0.042-0.119 0.05-0.182 0.051h-0.323l-3.407-2.914 0.453 2.575c0.028 0.143-0.041 0.24-0.099 0.281-0.06 0.046-0.135 0.07-0.208 0.07h-0.328l-1.23-6.62h0.728l0.539 2.93 2.698-2.932h0.323c0.064 0.002 0.126 0.011 0.181 0.053 0.056 0.039 0.086 0.117 0.084 0.18 0 0.119-0.097 0.239-0.097 0.239l-2.553 2.774 3.408 2.894s0.096 0.082 0.096 0.239c0.001 0.063-0.027 0.139-0.083 0.18m-6.841-0.942c-0.133 0.129-0.336 0.191-0.65 0.25-0.082 0.015-0.217 0.027-0.37 0.035-0.379 0.021-1.505 0.004-1.85 0.001l-0.436-2.408c0.259-0.004 0.556-0.008 0.765-0.009 0.589 0 1.159 0.019 1.562 0.129 1.037 0.281 1.551 1.457 0.979 2.002m0.669 0.4c0.295-0.335 0.317-0.957 0.233-1.34-0.102-0.468-0.547-1.649-2.215-1.798l1.405-2.407s0.096-0.144 0.096-0.239c0.001-0.063-0.027-0.141-0.084-0.18-0.056-0.042-0.117-0.053-0.18-0.053h-0.371l-1.648 2.84-1.327 0.006-0.515-2.846h-0.729l1.232 6.622h1.701c1.005 0 1.887-0.015 2.402-0.605m-48.958-0.809c-0.083-0.533 0.081-1.159 0.675-1.574l2.116-1.477c0.222-0.153 0.332-0.259 0.305-0.438-0.04-0.247-0.169-0.347-0.608-0.347h-2.461c-0.263 0-0.406-0.129-0.454-0.426v-0.917h2.883c1.264 0 1.889 0.745 2.015 1.537 0.118 0.745-0.132 1.277-0.613 1.62l-2.249 1.596c-0.181 0.13-0.226 0.197-0.213 0.295 0.02 0.162 0.168 0.229 0.424 0.229h2.57c0.314 0 0.493 0.321 0.543 0.639v0.705h-3.398c-0.754 0-1.413-0.674-1.535-1.442m25.616 1.443h-3.564l-0.018-0.107c-0.083-0.449-0.046-0.97 0.665-0.97h2.64c1.045 0 1.445-0.756 1.186-2.175-0.238-1.3-0.938-2.293-2.002-2.293h-1.431l0.679 3.724h-1.217c-0.164 0-0.277-0.119-0.296-0.225l-0.835-4.575h2.791c2.061 0 3.498 0.97 3.943 3.417 0.344 1.88-0.439 3.204-2.541 3.204m-8.461-5.546h-2.813l0.972 5.32c0.018 0.107-0.061 0.225-0.193 0.225h-1.238l-1.209-6.62h4.66l0.121 0.661c0.054 0.297-0.046 0.414-0.3 0.414m3.506 5.546h-1.238l-1.209-6.621h1.472l1.168 6.396c0.02 0.107-0.06 0.225-0.193 0.225m-11.449-1.135c-1.343 0-2.047-0.781-2.299-2.317-0.252-1.539 0.195-2.318 1.538-2.318s2.046 0.779 2.298 2.318c0.253 1.536-0.194 2.317-1.537 2.317m2.999-2.317c-0.309-1.88-1.581-3.454-3.946-3.454-2.367 0-3.122 1.574-2.813 3.454 0.308 1.879 1.581 3.451 3.947 3.451 2.365 0 3.121-1.572 2.812-3.451m-17.2 5.758c-1.407 0.231-3.922 0.32-4.782-0.998-0.664-1.018 0.591-2.413 1.734-3.764 0.916-1.082 1.913-2.116 1.506-2.72-0.332-0.494-2.463-0.493-3.197-0.532-0.891-0.047-2.175-0.05-2.399-0.172-0.201-0.108-0.313-0.444 0.441-0.579 0.606-0.109 2.049-0.33 3.571-0.329 1.589 0 3.258 0.261 3.62 1.491 0.291 0.988-0.718 2.047-1.605 3.121-0.97 1.176-1.954 2.014-1.871 2.847 0.063 0.648 1.334 0.685 3.442 0.722 0.438 0.008 1.311 0.029 1.143 0.375-0.15 0.306-0.803 0.407-1.603 0.538m-6.485-2.933c-0.902 0.619-2.51 0.826-3.834 0.757-1.179-0.062-3.206-0.437-3.13-0.81 0.072-0.403 1.355-0.278 1.943-0.278 1.293 0 3.345-0.079 3.668-0.912 0.697-1.795-4.029-5.018-5.037-4.708-0.456 0.139 0.926 2.482 1.427 3.324 0.298 0.503 0.752 1.293 0.365 1.347-0.433 0.06-0.91-0.684-1.22-1.113-0.841-1.165-1.702-2.437-2.393-3.744-0.214-0.404-0.922-1.619-0.679-1.776 0.227-0.146 1.308 0.062 2.683 0.607 4.322 1.711 6.268 3.941 6.789 5.783 0.183 0.649-0.133 1.215-0.582 1.523m-3.209 7.869c-0.089 0.291 1.353 0.669 2.934 0.739 1.593 0.07 3.477-0.247 3.829-1.298 0.575-1.722-2.342-4.233-4.883-5.467-0.624-0.302-1.001-0.385-1.159-0.364-0.149 0.02-0.173 0.16-0.124 0.256 0.09 0.181 0.501 0.55 1.049 0.951 2.861 2.09 3.791 3.623 3.251 4.244-0.347 0.398-1.797 0.675-3.303 0.675-0.409 0-1.483-0.089-1.594 0.264" fill="#ee2722"/>
</g>
</g></svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

-151
View File
@@ -1,151 +0,0 @@
#!/usr/bin/env node
// One-off loader for the production conversations + messages snapshot into
// a freshly-reset local Supabase. The schema lives in the migration at
// supabase/migrations/20260518000000_parametric_ai_sdk_parts.sql — that's
// where `_content_to_parts_v1` and the parts/metadata backfill UPDATE come
// from. This script just gets the CSV bytes into the database and then
// re-fires the same backfill UPDATE so the new rows pick up parts/metadata.
//
// Usage: NODE_PATH=/tmp/node_modules node scripts/load-prod-snapshot.mjs
import { createReadStream, createWriteStream, mkdirSync, existsSync } from 'fs';
import { writeFile } from 'fs/promises';
import { parse as parseStream } from 'csv-parse';
import { spawn } from 'child_process';
const PG_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
const RAW_CONVS = '/Users/dylan-at-adam/Downloads/migration_work/conversations_raw.csv';
const RAW_MSGS_CLEAN = '/Users/dylan-at-adam/Downloads/migration_work/messages_clean.csv';
const WORK = '/tmp/cadam_load';
if (!existsSync(WORK)) mkdirSync(WORK, { recursive: true });
function run(cmd, args, opts = {}) {
return new Promise((resolve, reject) => {
const p = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], ...opts });
let out = '', err = '';
p.stdout?.on('data', (c) => (out += c.toString()));
p.stderr?.on('data', (c) => (err += c.toString()));
p.on('close', (code) => {
if (code !== 0) reject(new Error(`${cmd} ${args.slice(0,3).join(' ')} exited ${code}: ${err}\n${out}`));
else resolve({ out, err });
});
p.on('error', reject);
});
}
function psql(sql) {
return run('psql', [PG_URL, '-v', 'ON_ERROR_STOP=1', '-c', sql]);
}
function psqlFile(path) {
return run('psql', [PG_URL, '-v', 'ON_ERROR_STOP=1', '-f', path]);
}
async function step(label, fn) {
const t = Date.now();
process.stdout.write(`[${label}] starting...\n`);
const res = await fn();
process.stdout.write(`[${label}] done in ${((Date.now() - t) / 1000).toFixed(1)}s\n`);
return res;
}
// 1. Scan conversations CSV → unique user_ids. The CSV columns already
// match the target table, so we'll \copy from the original file later.
const userIds = new Set();
const conversationIds = new Set();
await step('scan conversations for user_ids', async () => {
const parser = createReadStream(RAW_CONVS).pipe(parseStream({ columns: true, relax_quotes: true }));
let n = 0;
for await (const row of parser) {
if (row.user_id) userIds.add(row.user_id);
if (row.id) conversationIds.add(row.id);
n++;
if (n % 25000 === 0) process.stdout.write(` rows=${n} distinct_users=${userIds.size}\n`);
}
process.stdout.write(` total conversations=${n}, distinct user_ids=${userIds.size}\n`);
});
// 2. Generate auth.users SQL.
await step('write auth.users SQL', async () => {
const out = createWriteStream(`${WORK}/auth_users.sql`);
out.write(`-- Auto-generated dummy auth.users so FK references resolve.\n`);
out.write(`SET session_replication_role = replica;\n`); // silence on_auth_user_created trigger
let i = 0;
for (const id of userIds) {
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) continue;
out.write(
`INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at, updated_at, confirmation_token, email_change, email_change_token_new, recovery_token) VALUES ('00000000-0000-0000-0000-000000000000', '${id}', 'authenticated', 'authenticated', 'u_${id}@local.invalid', '', now(), '{"provider":"email"}'::jsonb, '{}'::jsonb, now(), now(), '', '', '', '') ON CONFLICT (id) DO NOTHING;\n`
);
i++;
}
out.write(`SET session_replication_role = DEFAULT;\n`);
out.end();
await new Promise((r) => out.on('close', r));
process.stdout.write(` wrote ${i} INSERT statements\n`);
});
// 3. Apply auth.users.
await step('create auth.users', () => psqlFile(`${WORK}/auth_users.sql`));
await step('truncate target tables', () => psql(
`SET session_replication_role = replica; TRUNCATE public.messages, public.conversations RESTART IDENTITY CASCADE; SET session_replication_role = DEFAULT;`
));
// 4. Stream raw CSVs into PostgreSQL with native \copy.
// - replica-mode silences update_leaf_trigger (otherwise the leaf-pointer
// update fires once per inserted message, dragging the load to a crawl).
// - we drop the existing indexes on `messages` before COPY and recreate
// them after, so PostgreSQL doesn't maintain them inline.
await step('drop messages indexes', () => psql(
`DROP INDEX IF EXISTS public.messages_conversation_id_idx;`
));
// `\copy` is a psql client-side meta-command; it can only run from stdin
// or a -f script, not from -c. Emit one tiny SQL script per table.
function copySql(table, columns, file) {
return `SET session_replication_role = replica;\n\\copy public.${table} (${columns.join(', ')}) FROM '${file}' WITH (FORMAT csv, HEADER true);\n`;
}
await step('\\copy conversations', async () => {
const sqlPath = `${WORK}/copy_conversations.sql`;
await writeFile(sqlPath, copySql('conversations',
['id','created_at','user_id','title','type','privacy','current_message_leaf_id','settings','updated_at'],
RAW_CONVS));
return psqlFile(sqlPath);
});
await step('\\copy messages', async () => {
const sqlPath = `${WORK}/copy_messages.sql`;
await writeFile(sqlPath, copySql('messages',
['id','created_at','conversation_id','role','content','rating','parent_message_id'],
RAW_MSGS_CLEAN));
return psqlFile(sqlPath);
});
await step('recreate messages indexes', () => psql(
`CREATE INDEX IF NOT EXISTS messages_conversation_id_idx ON public.messages USING btree (conversation_id);`
));
// 5. Backfill parts/metadata.
await step('backfill parts + metadata', () => psql(
`UPDATE public.messages m
SET parts = public._content_to_parts_v1(m.id, m.role, m.content, c.user_id, c.id),
metadata = public._content_to_metadata_v1(m.content)
FROM public.conversations c
WHERE c.id = m.conversation_id
AND m.content IS NOT NULL
AND m.parts = '[]'::jsonb;`
));
// 6. Verify counts and spot-check.
await step('verify counts', async () => {
const { out } = await psql(
`SELECT (SELECT count(*) FROM public.conversations) AS conv_n,
(SELECT count(*) FROM public.messages) AS msg_n,
(SELECT count(*) FROM public.messages WHERE jsonb_array_length(parts) > 0) AS msg_with_parts,
(SELECT count(*) FROM public.messages WHERE content IS NOT NULL AND jsonb_array_length(parts) = 0) AS msg_empty_parts;`
);
process.stdout.write(out);
});
process.stdout.write('Load complete.\n');
-83
View File
@@ -1,83 +0,0 @@
export type TreeNode<T> = T & {
children: TreeNode<T>[];
parent: TreeNode<T> | null;
get siblings(): TreeNode<T>[];
};
interface TreeElement {
id: string;
parent_message_id: string | null;
}
class Tree<T extends TreeElement> {
allNodes: Map<string, TreeNode<T>> = new Map();
rootNodes: TreeNode<T>[] = [];
constructor(elements: T[]) {
const nodes: Map<string, TreeNode<T>> = new Map(); // UUID -> node
const rootNodes: TreeNode<T>[] = [];
// First pass: Create all nodes
elements.forEach((element) => {
const node: TreeNode<T> = {
...element,
children: [],
parent: null,
get siblings() {
return this.parent ? this.parent.children : rootNodes;
},
};
nodes.set(element.id, node);
});
// Second pass: Build parent-child relationships
elements.forEach((element) => {
const node = nodes.get(element.id);
if (node) {
if (element.parent_message_id) {
const parentNode = nodes.get(element.parent_message_id);
if (parentNode) {
parentNode.children.push(node);
node.parent = parentNode;
}
} else {
// No parent means this is a root node
rootNodes.push(node);
}
}
});
this.allNodes = nodes;
this.rootNodes = rootNodes;
}
getPath(id: string): TreeNode<T>[] {
const path: TreeNode<T>[] = [];
// Visited-set defense against `parent_message_id` cycles in the data
// (e.g. a row that points at itself). Without this guard `getPath`
// walks the chain forever and locks the entire UI. The server contract
// shouldn't allow cycles to land in the DB, but the tree is rendered
// straight from a Supabase query so we treat it as untrusted.
const visited = new Set<string>();
let currentNode = this.allNodes.get(id);
while (currentNode) {
if (visited.has(currentNode.id)) {
console.warn(
`[Tree.getPath] cycle detected at message ${currentNode.id} — truncating walk`,
);
break;
}
visited.add(currentNode.id);
path.unshift(currentNode);
if (currentNode.parent) {
currentNode = currentNode.parent;
} else {
break;
}
}
return path;
}
}
export default Tree;
-137
View File
@@ -1,137 +0,0 @@
import { tool, type InferUITools, type UIMessage } from 'ai';
import { z } from 'zod';
import type { MeshFileType, Model } from './types.ts';
export const createMeshInputSchema = z.object({
text: z.string().optional(),
imageIds: z.array(z.string()).optional(),
meshId: z.string().optional(),
model: z.enum(['fast', 'quality', 'ultra']).optional(),
meshTopology: z.enum(['quads', 'polys']).optional(),
polygonCount: z.number().optional(),
});
export const createMeshOutputSchema = z.object({
id: z.string(),
fileType: z.enum(['glb', 'stl', 'obj', 'fbx']),
});
export const parametricArtifactSchema = z.object({
title: z.string().min(1),
version: z.string().default('v1'),
code: z.string().min(20),
});
export const parametricCompileOutputSchema = z.object({
status: z.literal('success'),
message: z.string(),
inspection: z
.object({
views: z.array(
z.enum(['ISO', 'FRONT', 'BACK', 'LEFT', 'RIGHT', 'TOP', 'BOTTOM']),
),
imageAttached: z.boolean(),
})
.optional(),
});
export const answerUserSchema = z.object({
message: z.string().min(1),
});
export const chatTools = {
build_parametric_model: tool({
description:
'Create or update the complete OpenSCAD CAD artifact. After the browser compiles it, inspect the returned multi-view preview sheet and call this tool again if the model needs another revision.',
inputSchema: parametricArtifactSchema,
outputSchema: parametricCompileOutputSchema,
}),
answer_user: tool({
description:
'Send the final user-facing chat message. Use this for normal non-CAD replies, and after a CAD build when the multi-view preview satisfies the user request.',
inputSchema: answerUserSchema,
outputSchema: answerUserSchema,
}),
create_mesh: tool({
description:
'Create a 3D mesh from text, images, or an existing mesh plus edit instructions.',
inputSchema: createMeshInputSchema,
outputSchema: createMeshOutputSchema,
}),
};
export type AppTools = InferUITools<typeof chatTools>;
export type MeshContextData = {
meshId: string;
fileType: MeshFileType;
filename?: string;
boundingBox?: { x: number; y: number; z: number };
};
export type MeshPreferencesData = {
topology: 'quads' | 'polys';
polygonCount: number;
};
/**
* Conversation-level signals the server emits as transient stream parts
* (`writer.write({ transient: true, type: 'data-X', data })`). Transient
* parts never land in `messages.parts` — they're side-channel updates the
* client folds straight into the conversation query cache.
*
* * `title-update` fires once when the server generates a title for
* a fresh conversation; client updates `conversations.title`.
* * `suggestions-update` fires after each assistant turn finishes;
* client updates `conversations.settings.suggestions` so the pills
* below the input refresh in lock-step with the response.
*/
export type ConversationTitleUpdate = {
conversationId: string;
title: string;
};
export type ConversationSuggestionsUpdate = {
conversationId: string;
suggestions: string[];
};
export type AppDataTypes = {
'mesh-context': MeshContextData;
'mesh-preferences': MeshPreferencesData;
'title-update': ConversationTitleUpdate;
'suggestions-update': ConversationSuggestionsUpdate;
};
export const meshContextDataSchema = z.object({
meshId: z.string(),
fileType: z.enum(['glb', 'stl', 'obj', 'fbx']),
filename: z.string().optional(),
boundingBox: z
.object({ x: z.number(), y: z.number(), z: z.number() })
.optional(),
});
export const meshPreferencesDataSchema = z.object({
topology: z.enum(['quads', 'polys']),
polygonCount: z.number(),
});
export type AppUIMessage = UIMessage<
{
model?: Model;
billingTokens?: number;
// The model's original OpenSCAD for this message's artifact, captured
// lazily on the FIRST parameter edit (see `persistParameterEdit`).
// Parameter edits rewrite the live `tool-build_parametric_model` input
// code in place, which would otherwise move the derived `defaultValue`
// to the edited value on every reload. Stashing the original here —
// message metadata is UI-only and NOT sent to the model by
// `convertToModelMessages` — lets the client re-derive stable defaults
// (Reset / slider home / auto range) with no second code copy in the
// model's context, no migration, and no storage cost on the (common)
// never-edited artifacts.
originalCode?: string;
},
AppDataTypes,
AppTools
>;
-447
View File
@@ -1,447 +0,0 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[];
export type Database = {
graphql_public: {
Tables: {
[_ in never]: never;
};
Views: {
[_ in never]: never;
};
Functions: {
graphql: {
Args: {
extensions?: Json;
operationName?: string;
query?: string;
variables?: Json;
};
Returns: Json;
};
};
Enums: {
[_ in never]: never;
};
CompositeTypes: {
[_ in never]: never;
};
};
public: {
Tables: {
conversations: {
Row: {
created_at: string | null;
current_message_leaf_id: string | null;
id: string;
privacy: Database['public']['Enums']['privacy_type'];
settings: Json;
title: string;
type: Database['public']['Enums']['conversation-type'];
updated_at: string | null;
user_id: string;
};
Insert: {
created_at?: string | null;
current_message_leaf_id?: string | null;
id?: string;
privacy?: Database['public']['Enums']['privacy_type'];
settings?: Json;
title: string;
type?: Database['public']['Enums']['conversation-type'];
updated_at?: string | null;
user_id: string;
};
Update: {
created_at?: string | null;
current_message_leaf_id?: string | null;
id?: string;
privacy?: Database['public']['Enums']['privacy_type'];
settings?: Json;
title?: string;
type?: Database['public']['Enums']['conversation-type'];
updated_at?: string | null;
user_id?: string;
};
Relationships: [];
};
images: {
Row: {
conversation_id: string;
created_at: string;
id: string;
image_generation_call_id: string | null;
prompt: Json;
status: Database['public']['Enums']['generation-status'];
user_id: string;
};
Insert: {
conversation_id: string;
created_at?: string;
id?: string;
image_generation_call_id?: string | null;
prompt?: Json;
status?: Database['public']['Enums']['generation-status'];
user_id: string;
};
Update: {
conversation_id?: string;
created_at?: string;
id?: string;
image_generation_call_id?: string | null;
prompt?: Json;
status?: Database['public']['Enums']['generation-status'];
user_id?: string;
};
Relationships: [
{
foreignKeyName: 'images_conversation_id_fkey';
columns: ['conversation_id'];
isOneToOne: false;
referencedRelation: 'conversations';
referencedColumns: ['id'];
},
];
};
meshes: {
Row: {
conversation_id: string;
created_at: string;
file_type: Database['public']['Enums']['mesh_file_type'];
id: string;
images: string[] | null;
prompt: Json;
status: Database['public']['Enums']['generation-status'];
user_id: string;
};
Insert: {
conversation_id: string;
created_at?: string;
file_type?: Database['public']['Enums']['mesh_file_type'];
id?: string;
images?: string[] | null;
prompt?: Json;
status?: Database['public']['Enums']['generation-status'];
user_id: string;
};
Update: {
conversation_id?: string;
created_at?: string;
file_type?: Database['public']['Enums']['mesh_file_type'];
id?: string;
images?: string[] | null;
prompt?: Json;
status?: Database['public']['Enums']['generation-status'];
user_id?: string;
};
Relationships: [
{
foreignKeyName: 'meshes_conversation_id_fkey';
columns: ['conversation_id'];
isOneToOne: false;
referencedRelation: 'conversations';
referencedColumns: ['id'];
},
];
};
messages: {
Row: {
conversation_id: string;
content: Json | null;
created_at: string;
id: string;
metadata: Json;
parent_message_id: string | null;
parts: Json;
rating: number;
role: string;
};
Insert: {
conversation_id: string;
content?: Json | null;
created_at?: string;
id?: string;
metadata?: Json;
parent_message_id?: string | null;
parts?: Json;
rating?: number;
role: string;
};
Update: {
conversation_id?: string;
content?: Json | null;
created_at?: string;
id?: string;
metadata?: Json;
parent_message_id?: string | null;
parts?: Json;
rating?: number;
role?: string;
};
Relationships: [
{
foreignKeyName: 'messages_conversation_id_fkey';
columns: ['conversation_id'];
isOneToOne: false;
referencedRelation: 'conversations';
referencedColumns: ['id'];
},
];
};
previews: {
Row: {
conversation_id: string;
created_at: string;
id: string;
mesh_id: string;
status: Database['public']['Enums']['generation-status'];
updated_at: string;
user_id: string;
};
Insert: {
conversation_id: string;
created_at?: string;
id?: string;
mesh_id: string;
status?: Database['public']['Enums']['generation-status'];
updated_at?: string;
user_id: string;
};
Update: {
conversation_id?: string;
created_at?: string;
id?: string;
mesh_id?: string;
status?: Database['public']['Enums']['generation-status'];
updated_at?: string;
user_id?: string;
};
Relationships: [
{
foreignKeyName: 'previews_conversation_id_fkey';
columns: ['conversation_id'];
isOneToOne: false;
referencedRelation: 'conversations';
referencedColumns: ['id'];
},
{
foreignKeyName: 'previews_mesh_id_fkey';
columns: ['mesh_id'];
isOneToOne: false;
referencedRelation: 'meshes';
referencedColumns: ['id'];
},
];
};
profiles: {
Row: {
avatar_path: string | null;
created_at: string;
full_name: string;
id: string;
notifications_enabled: boolean;
updated_at: string;
user_id: string;
};
Insert: {
avatar_path?: string | null;
created_at?: string;
full_name: string;
id?: string;
notifications_enabled?: boolean;
updated_at?: string;
user_id: string;
};
Update: {
avatar_path?: string | null;
created_at?: string;
full_name?: string;
id?: string;
notifications_enabled?: boolean;
updated_at?: string;
user_id?: string;
};
Relationships: [];
};
prompts: {
Row: {
created_at: string;
id: number;
type: Database['public']['Enums']['prompt_type'];
user_id: string;
};
Insert: {
created_at?: string;
id?: number;
type?: Database['public']['Enums']['prompt_type'];
user_id: string;
};
Update: {
created_at?: string;
id?: number;
type?: Database['public']['Enums']['prompt_type'];
user_id?: string;
};
Relationships: [];
};
};
Views: {
[_ in never]: never;
};
Functions: {
[_ in never]: never;
};
Enums: {
'conversation-type': 'parametric' | 'creative';
'generation-status': 'pending' | 'success' | 'failure';
mesh_file_type: 'glb' | 'stl' | 'obj' | 'fbx';
mesh_model_type: 'quality' | 'fast';
privacy_type: 'public' | 'private';
prompt_type: 'mesh' | 'image' | 'chat';
};
CompositeTypes: {
[_ in never]: never;
};
};
};
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>;
type DefaultSchema = DatabaseWithoutInternals[Extract<
keyof Database,
'public'
>];
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views'])
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends {
Row: infer R;
}
? R
: never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] &
DefaultSchema['Views'])
? (DefaultSchema['Tables'] &
DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R;
}
? R
: never
: never;
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema['Tables']
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Insert: infer I;
}
? I
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I;
}
? I
: never
: never;
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema['Tables']
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Update: infer U;
}
? U
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Update: infer U;
}
? U
: never
: never;
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema['Enums']
| { schema: keyof DatabaseWithoutInternals },
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums']
: never = never,
> = DefaultSchemaEnumNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums']
? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions]
: never;
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema['CompositeTypes']
| { schema: keyof DatabaseWithoutInternals },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes']
: never = never,
> = PublicCompositeTypeNameOrOptions extends {
schema: keyof DatabaseWithoutInternals;
}
? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes']
? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions]
: never;
export const Constants = {
graphql_public: {
Enums: {},
},
public: {
Enums: {
'conversation-type': ['parametric', 'creative'],
'generation-status': ['pending', 'success', 'failure'],
mesh_file_type: ['glb', 'stl', 'obj', 'fbx'],
mesh_model_type: ['quality', 'fast'],
privacy_type: ['public', 'private'],
prompt_type: ['mesh', 'image', 'chat'],
},
},
} as const;
-46
View File
@@ -1,46 +0,0 @@
// Helpers for resolving uploaded-image file parts to private-storage objects.
//
// Uploaded images live in the (private, RLS-protected) `images` bucket at
// `${userId}/${conversationId}/${imageId}`. An AI SDK file part carries the
// image id in its `filename` (`${imageId}.png`); the part's `url` is a stable
// REFERENCE string, not something to fetch directly. The bytes are resolved
// from storage by id at the two boundaries that actually need them:
// * the chat server downloads them to base64 for the model, and
// * the client downloads them via a signed URL for display.
//
// Persisting a base64 data URL in the part (as the AI SDK migration
// accidentally did) duplicates the whole image into `messages.parts`; a raw
// `/storage/.../public/...` path (as the backfill wrote) never resolves
// because the bucket is private. Both are avoided by keeping `url` a
// reference and resolving by id.
export function imageIdFromFilename(
filename: string | null | undefined,
): string | null {
if (!filename) return null;
return filename.replace(/\.[^.]+$/, '') || null;
}
export function imageStoragePath(
userId: string,
conversationId: string,
imageId: string,
): string {
return `${userId}/${conversationId}/${imageId}`;
}
// Canonical reference persisted in a file part's `url`. Kept in the exact
// shape the 2026-05-18 AI-SDK backfill produced so every row in
// `messages.parts` carries a single, uniform format. Never fetched directly —
// see the note above.
export function imageFilePartUrl(
userId: string,
conversationId: string,
imageId: string,
): string {
return `/storage/v1/object/public/images/${imageStoragePath(
userId,
conversationId,
imageId,
)}`;
}
-12
View File
@@ -1,12 +0,0 @@
import type { Model } from './types';
// Model ids persisted in conversation settings (and submitted by stale
// clients) outlive the picker catalog. Map retired ids to their successors
// so old conversations keep resolving to a routable, correctly priced model.
export const LEGACY_MODEL_IDS: Record<string, Model> = {
'openai/gpt-5.5': 'openai/gpt-5.6-sol',
};
export function normalizeModelId(model: Model): Model {
return LEGACY_MODEL_IDS[model] ?? model;
}
-21
View File
@@ -1,21 +0,0 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { cleanAssistantText } from './parametricParts.ts';
describe('parametric assistant text cleanup', () => {
it('removes leaked view metadata before final prose', () => {
assert.equal(
cleanAssistantText(
', viewpoint_state:{"distance":624},"zoom_info":A fallback automatic framing was used instead.}ளர்This 12 DOF robot arm is ready.',
),
'This 12 DOF robot arm is ready.',
);
});
it('removes metadata-only fragments', () => {
assert.equal(
cleanAssistantText(',title:Detailed San Francisco,version:v1}'),
'',
);
});
});
-186
View File
@@ -1,186 +0,0 @@
import type {
AppUIMessage,
MeshContextData,
MeshPreferencesData,
} from './chatAi.ts';
import type { ParametricArtifact } from './types.ts';
/**
* Narrow an unknown DB jsonb value into the AI SDK's UI part array.
*
* Every callsite (`messages.parts` from supabase, draft message rows held
* in cache, etc.) crosses an untrusted boundary, so a bare
* `parts as AppUIMessage['parts']` would let a malformed row or an
* upstream SDK shape change crash the renderer before any part-specific
* narrowing runs.
*
* Element-level validation is intentionally minimal: we require each
* element to be a non-null object with a string `type` discriminator,
* which is what every downstream `switch (part.type)` already keys on.
* Beyond that we trust the SDK union — adding a full zod schema for the
* dozen+ part shapes would have to be kept in lock-step with the AI SDK
* release on every bump, and silently rejected parts (e.g. a new
* `source-document`) would degrade messages instead of just rendering
* what we know how to render.
*/
export function asParametricParts(parts: unknown): AppUIMessage['parts'] {
if (!Array.isArray(parts)) return [];
return parts.filter(
(part): part is AppUIMessage['parts'][number] =>
typeof part === 'object' &&
part !== null &&
'type' in part &&
typeof (part as { type: unknown }).type === 'string',
);
}
export function getMeshContextPart(
parts: unknown,
): MeshContextData | undefined {
const list = asParametricParts(parts);
for (let index = list.length - 1; index >= 0; index -= 1) {
const part = list[index];
if (part.type === 'data-mesh-context') return part.data;
}
return undefined;
}
export function getMeshPreferencesPart(
parts: unknown,
): MeshPreferencesData | undefined {
const list = asParametricParts(parts);
for (let index = list.length - 1; index >= 0; index -= 1) {
const part = list[index];
if (part.type === 'data-mesh-preferences') return part.data;
}
return undefined;
}
export function getParametricText(parts: unknown): string {
return asParametricParts(parts)
.filter((part) => part.type === 'text')
.map((part) => cleanAssistantText(part.text))
.join('');
}
export function cleanAssistantText(text: string): string {
text = text.replace(/!\[[^\]]*]\([^)]+\)/g, '');
const metadataLeak =
/(?:^|\n)\s*,?\s*(?:"?(?:viewpoint_state|zoom_info|title|version)"?\s*:)/i.exec(
text,
);
if (metadataLeak) {
const before = text.slice(0, metadataLeak.index);
const leaked = text.slice(metadataLeak.index);
const proseStart =
/\b(?:Done|Here(?:'s| is)?|This|I(?:'ve| created| updated| made| added| fixed)|The model)\b/i.exec(
leaked,
);
text = before + (proseStart ? leaked.slice(proseStart.index) : '');
}
const attachmentLeak =
/(?:^|\n)[^\n{}]*(?:alt=media|preview sheet attached automatically)[^\n{}]*[})]?\s*/i.exec(
text,
);
if (attachmentLeak) {
text =
text.slice(0, attachmentLeak.index) +
text.slice(attachmentLeak.index + attachmentLeak[0].length);
}
text = text.replace(
/(?:^|\n)\s*[^{}\n]*(?:\.png|\.jpe?g|\.webp|\.gif)[^{}\n]*[})]?\s*/gi,
'\n',
);
const marker = /Drafting final message:\s*/i.exec(text);
if (!marker) return text;
const draft = text.slice(marker.index + marker[0].length).trimStart();
if (!draft) return '';
const quote = draft[0];
if (quote !== '"' && quote !== "'") return draft;
const quoteEnd = draft.indexOf(quote, 1);
if (quoteEnd === -1) return draft.slice(1).trim();
return draft.slice(1, quoteEnd).trim();
}
export function getBuildParametricModelPart(parts: unknown) {
const parametricParts = asParametricParts(parts);
for (let index = parametricParts.length - 1; index >= 0; index -= 1) {
const part = parametricParts[index];
if (part.type === 'tool-build_parametric_model') return part;
}
return undefined;
}
export function getBuildParametricModelOutput(
parts: unknown,
): ParametricArtifact | undefined {
const part = getBuildParametricModelPart(parts);
if (!part || part.state === 'input-streaming') return undefined;
if ('input' in part && isParametricArtifact(part.input)) {
return part.input;
}
if (part.state === 'output-available' && isParametricArtifact(part.output)) {
return part.output;
}
return undefined;
}
export const getBuildParametricModelArtifact = getBuildParametricModelOutput;
export function hasPendingBuildParametricModel(parts: unknown): boolean {
const part = getBuildParametricModelPart(parts);
return part?.state === 'input-streaming' || part?.state === 'input-available';
}
export function replaceBuildParametricModelOutput(
parts: unknown,
artifact: ParametricArtifact,
): AppUIMessage['parts'] {
const parametricParts = asParametricParts(parts);
let targetIndex = -1;
for (let index = parametricParts.length - 1; index >= 0; index -= 1) {
const part = parametricParts[index];
if (
part.type === 'tool-build_parametric_model' &&
part.state !== 'input-streaming'
) {
targetIndex = index;
break;
}
}
return parametricParts.map((part, index) => {
if (
index === targetIndex &&
part.type === 'tool-build_parametric_model' &&
part.state !== 'input-streaming'
) {
return { ...part, input: artifact };
}
return part;
});
}
export function isParametricArtifact(
value: unknown,
): value is ParametricArtifact {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false;
}
const artifact = value as Partial<ParametricArtifact>;
// Title + code are the only load-bearing fields. `version` is metadata
// and `parts` is optional. Parameters are derived client-side from
// `code` via `parseParameters` so we don't check for them here either.
return (
typeof artifact.title === 'string' && typeof artifact.code === 'string'
);
}
-286
View File
@@ -1,286 +0,0 @@
import type {
Parameter,
ParameterOption,
ParameterRange,
ParameterType,
} from './types.ts';
/**
* Extract editable parameters from a piece of OpenSCAD source.
*
* This is the single source of truth for "what does this CAD model expose
* as a slider/input?" — the model only emits the OpenSCAD `code`, and we
* derive parameter metadata client-side from the variable declarations at
* the top of the file. That removes the divergent-UI problem we had when
* different models (Claude vs Gemini) produced different shaped
* parameter arrays for the same code: now the same source always renders
* the same `<ParameterSection>`, regardless of provider.
*
* The format the model is told to emit is the Customizer-style annotation
* vocabulary:
*
* // Description of the parameter
* name = 10; // [1:50] ← min:max
* name = 10; // [1:1:50] ← min:step:max
* name = "red"; // [red, green, blue] ← enum options
* name = "label"; // 20 ← maxLength for strings, step for numbers
* \/* [Group Name] *\/ ← starts a new group section
*
* Variable declarations after the first `module` or `function` keyword
* are NOT exposed (they're implementation, not API).
*
* TODO: Use AST parser instead of regex. Regex breaks on multi-line
* expressions, nested arrays, and any clever OpenSCAD trick. An AST
* parser would handle that gracefully — for now, the regex covers the
* shapes the model actually emits.
*/
export default function parseParameters(script: string): Parameter[] {
// Limit to the top of the file. Anything below the first `module` or
// `function` is internal logic that the user shouldn't tweak as a
// parameter.
script = script.split(/^(module |function )/m)[0];
const parameters: Record<string, Parameter> = {};
const parameterRegex =
/^([a-z0-9A-Z_$]+)\s*=\s*([^;]+);[\t\f\cK ]*(\/\/[^\n]*)?/gm;
const groupRegex = /^\/\*\s*\[([^\]]+)\]\s*\*\//gm;
// Build a list of source ranges keyed by group, so a `/* [Group] */`
// marker influences only the variables declared below it. We track each
// group's start offset directly off the regex match position — using the
// marker text as an id and then `indexOf`-ing it would collide whenever
// the same group label appears twice in the source.
const groupSections: { startIndex: number; group: string; code: string }[] = [
{ startIndex: 0, group: '', code: script },
];
let tmpGroup: RegExpExecArray | null;
while ((tmpGroup = groupRegex.exec(script))) {
groupSections.push({
startIndex: tmpGroup.index,
group: tmpGroup[1].trim(),
code: '',
});
}
groupSections.forEach((group, index) => {
const nextGroup = groupSections[index + 1];
const endIndex = nextGroup ? nextGroup.startIndex : script.length;
group.code = script.substring(group.startIndex, endIndex);
});
groupSections.forEach((groupSection) => {
let match;
while ((match = parameterRegex.exec(groupSection.code)) !== null) {
const name = match[1];
const value = match[2];
let typeAndValue:
| { value: Parameter['value']; type: Parameter['type'] }
| undefined;
try {
typeAndValue = convertType(value);
} catch {
continue;
}
if (!typeAndValue) continue;
let description: Parameter['description'] = undefined;
let options: ParameterOption[] = [];
let range: ParameterRange = {};
// Skip values that reference another variable or span lines —
// they're computed expressions, not constants. Once we hit one,
// bail out of THIS group section because anything further is
// probably derived from it.
if (
value !== 'true' &&
value !== 'false' &&
(value.match(/^[a-zA-Z_]/) || value.split('\n').length > 1)
) {
continue;
}
// The trailing `// ...` comment carries Customizer-style hints.
if (match[3]) {
const rawComment = match[3].replace(/^\/\/\s*/, '').trim();
const cleaned = rawComment.replace(/^\[+|\]+$/g, '');
if (!isNaN(Number(rawComment))) {
// Bare number — step for numerics, maxLength for strings.
if (typeAndValue.type === 'string') {
range = { max: parseFloat(cleaned) };
} else {
range = { step: parseFloat(cleaned) };
}
} else if (rawComment.startsWith('[') && cleaned.includes(',')) {
// `[a, b:Label, c]` — enum options. `value:Label` lets the
// model pick a human label distinct from the underlying value.
options = cleaned
.trim()
.split(',')
.map((option) => {
const parts = option.trim().split(':');
let optionValue: ParameterOption['value'] = parts[0];
const label: ParameterOption['label'] = parts[1];
if (typeAndValue.type === 'number') {
optionValue = parseFloat(optionValue);
}
return { value: optionValue, label };
});
} else if (cleaned.match(/([0-9]+:?)+/)) {
// `[min:max]` or `[min:step:max]` — slider bounds.
const [min, maxOrStep, max] = cleaned.trim().split(':');
if (min && (maxOrStep || max)) {
range = { min: parseFloat(min) };
}
if (max || maxOrStep || min) {
range = { ...range, max: parseFloat(max || maxOrStep || min) };
}
if (max && maxOrStep) {
range = { ...range, step: parseFloat(maxOrStep) };
}
}
}
// The description is the last `// ...` comment on the line
// IMMEDIATELY above the variable declaration.
let above = script.split(
new RegExp(`^${escapeRegExp(match[0])}`, 'gm'),
)[0];
if (above.endsWith('\n')) above = above.slice(0, -1);
const splitted = above.split('\n').reverse();
const lastLineBeforeDefinition = splitted[0];
if (lastLineBeforeDefinition.trim().startsWith('//')) {
description = lastLineBeforeDefinition.replace(/^\/\/\/*\s*/, '');
if (description.length === 0) description = undefined;
}
// Snake_case → Title Case for the visible label. `$fn` gets a
// special name because OpenSCAD users recognise it as resolution.
// Filtering empty tokens guards against names with leading, trailing,
// or repeated underscores (e.g. `__width`) producing `word[0]` on an
// empty string and crashing.
let displayName = name
.replace(/_/g, ' ')
.split(' ')
.filter(Boolean)
.map((word) => word[0].toUpperCase() + word.slice(1))
.join(' ');
if (name === '$fn') displayName = 'Resolution';
// Flatten `name = [a, b, c]` (number[]) into N scalar sliders
// `name[0]`, `name[1]`, ... — far easier to manipulate via the
// sidebar than a single multi-value field.
if (
typeAndValue.type === 'number[]' &&
Array.isArray(typeAndValue.value)
) {
const labels = numberArrayLabels(name, typeAndValue.value.length);
typeAndValue.value.forEach((itemValue, index) => {
parameters[`${name}[${index}]`] = {
description,
group: groupSection.group,
name: `${name}[${index}]`,
displayName: displayNameForArrayItem(displayName, labels[index]),
defaultValue: itemValue,
range,
options,
value: itemValue,
type: 'number',
};
});
continue;
}
parameters[name] = {
description,
group: groupSection.group,
name,
displayName,
defaultValue: typeAndValue.value,
range,
options,
...typeAndValue,
};
}
});
return Object.values(parameters);
}
function numberArrayLabels(name: string, length: number) {
if (length === 2) return ['X', 'Y'];
if (length !== 3) return Array.from({ length }, (_, i) => `${i + 1}`);
const lowerName = name.toLowerCase();
if (
lowerName.includes('size') ||
lowerName.includes('dimension') ||
lowerName.includes('body') ||
lowerName.includes('torso') ||
lowerName.includes('head') ||
lowerName.includes('foot') ||
lowerName.includes('base')
) {
return ['Width', 'Depth', 'Height'];
}
return ['X', 'Y', 'Z'];
}
function displayNameForArrayItem(displayName: string, label: string) {
if (['Width', 'Depth', 'Height'].includes(label)) {
return displayName.replace(/\s+Size$/i, '') + ` ${label}`;
}
return `${displayName} ${label}`;
}
function convertType(rawValue: string): {
value: Parameter['value'];
type: ParameterType;
} {
if (/^-?\d+(\.\d+)?$/.test(rawValue)) {
return { value: parseFloat(rawValue), type: 'number' };
}
if (rawValue === 'true' || rawValue === 'false') {
return { value: rawValue === 'true', type: 'boolean' };
}
if (/^".*"$/.test(rawValue)) {
return { value: rawValue.replace(/^"(.*)"$/, '$1'), type: 'string' };
}
if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
const arrayValue = rawValue
.slice(1, -1)
.split(',')
.map((item) => item.trim());
if (
arrayValue.length > 0 &&
arrayValue.every((item) => /^-?\d+(\.\d+)?$/.test(item))
) {
return {
value: arrayValue.map((item) => parseFloat(item)),
type: 'number[]',
};
}
if (
arrayValue.length > 0 &&
arrayValue.every((item) => /^".*"$/.test(item))
) {
return {
value: arrayValue.map((item) => item.slice(1, -1)),
type: 'string[]',
};
}
if (
arrayValue.length > 0 &&
arrayValue.every((item) => item === 'true' || item === 'false')
) {
return {
value: arrayValue.map((item) => item === 'true'),
type: 'boolean[]',
};
}
throw new Error(`Invalid array value: ${rawValue}`);
}
throw new Error(`Invalid value: ${rawValue}`);
}
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
-61
View File
@@ -1,61 +0,0 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
countSuggestionWords,
limitSuggestionWords,
normalizeConversationSuggestions,
} from './suggestions.ts';
describe('suggestion word limits', () => {
it('counts whitespace-separated words', () => {
assert.equal(countSuggestionWords(' add mounting holes '), 3);
assert.equal(countSuggestionWords(''), 0);
});
it('treats punctuation and hyphenated terms as part of a word', () => {
assert.equal(countSuggestionWords('make snap-fit tabs'), 3);
assert.equal(countSuggestionWords('add ribs, fillets'), 3);
});
it('limits suggestions to three words', () => {
assert.equal(
limitSuggestionWords('make the brackets thicker'),
'make the brackets',
);
});
it('keeps valid suggestions before truncating fallback suggestions', () => {
assert.deepEqual(
normalizeConversationSuggestions([
'make the brackets much thicker',
'add fillets',
' add screw holes ',
'increase wall thickness',
]),
['add fillets', 'add screw holes'],
);
});
it('deduplicates accepted suggestions before filling slots', () => {
assert.deepEqual(
normalizeConversationSuggestions([
'add fillets',
'add fillets',
'add screw holes',
'increase wall thickness',
]),
['add fillets', 'add screw holes'],
);
});
it('truncates invalid suggestions only when needed to fill two slots', () => {
assert.deepEqual(
normalizeConversationSuggestions([
'add chamfers',
'make the handle much larger',
'add four mounting holes',
]),
['add chamfers', 'make the handle'],
);
});
});
-51
View File
@@ -1,51 +0,0 @@
const MAX_SUGGESTION_WORDS = 3;
const MAX_SUGGESTIONS = 2;
export function countSuggestionWords(suggestion: string): number {
const trimmed = suggestion.trim();
if (!trimmed) return 0;
return trimmed.split(/\s+/).length;
}
export function limitSuggestionWords(
suggestion: string,
maxWords = MAX_SUGGESTION_WORDS,
): string {
return suggestion.trim().split(/\s+/).slice(0, maxWords).join(' ');
}
export function normalizeConversationSuggestions(
suggestions: string[],
maxSuggestions = MAX_SUGGESTIONS,
): string[] {
const trimmedSuggestions = suggestions
.map((suggestion) => suggestion.trim())
.filter(Boolean);
const accepted = Array.from(
new Set(
trimmedSuggestions.filter(
(suggestion) =>
countSuggestionWords(suggestion) <= MAX_SUGGESTION_WORDS,
),
),
);
if (accepted.length >= maxSuggestions) {
return accepted.slice(0, maxSuggestions);
}
const seen = new Set(accepted);
const fallback = trimmedSuggestions
.filter(
(suggestion) => countSuggestionWords(suggestion) > MAX_SUGGESTION_WORDS,
)
.map((suggestion) => limitSuggestionWords(suggestion))
.filter((suggestion) => {
if (!suggestion || seen.has(suggestion)) return false;
seen.add(suggestion);
return true;
});
return [...accepted, ...fallback].slice(0, maxSuggestions);
}
-93
View File
@@ -1,93 +0,0 @@
import { Database } from './database.ts';
import type { AppUIMessage } from './chatAi.ts';
export type Model = string;
export type CreativeModel = 'quality' | 'fast' | 'ultra';
export type Prompt = {
text?: string;
images?: string[];
mesh?: string;
model?: Model;
};
type MessageRow = Database['public']['Tables']['messages']['Row'];
export type Message = Pick<
MessageRow,
'conversation_id' | 'created_at' | 'id' | 'parent_message_id' | 'rating'
> & {
role: 'user' | 'assistant';
metadata: AppUIMessage['metadata'];
parts: AppUIMessage['parts'];
};
export type MeshFileType = Database['public']['Enums']['mesh_file_type'];
export type Mesh = {
id: string;
fileType: MeshFileType;
};
export type MeshData = Omit<
Database['public']['Tables']['meshes']['Row'],
'prompt'
> & {
prompt: Prompt;
};
export type ParametricArtifact = {
title: string;
version: string;
code: string;
};
// label is optional: an OpenSCAD customizer comment can list bare values
// (e.g. `[Assembled, Exploded]`) with no `value:label` pair, in which case
// the parser leaves label undefined and the UI falls back to the value.
export type ParameterOption = { value: string | number; label?: string };
export type ParameterRange = { min?: number; max?: number; step?: number };
export type ParameterType =
| 'string'
| 'number'
| 'boolean'
| 'string[]'
| 'number[]'
| 'boolean[]';
export type Parameter = {
name: string;
displayName: string;
value: string | boolean | number | string[] | number[] | boolean[];
defaultValue: string | boolean | number | string[] | number[] | boolean[];
// Type should always exist, but old messages don't have it.
type?: ParameterType;
description?: string;
group?: string;
range?: ParameterRange;
options?: ParameterOption[];
maxLength?: number;
};
export type Conversation = Omit<
Database['public']['Tables']['conversations']['Row'],
'settings'
> & {
settings: ConversationSettings;
};
export type GenerationStatus = Database['public']['Enums']['generation-status'];
export type ConversationSettings = {
model?: Model;
/**
* Per-conversation follow-up suggestions rendered as pills above the
* chat input. Regenerated server-side after each non-tool-call
* assistant turn — see `emitConversationSuggestions` in
* `src/server/aiChat.ts`.
*/
suggestions?: string[];
} | null;
export type Profile = Database['public']['Tables']['profiles']['Row'];
-45
View File
@@ -1,45 +0,0 @@
import { AuthProvider } from '@/contexts/AuthProvider';
import { TooltipProvider } from './components/ui/tooltip';
import { Toaster } from './components/ui/toaster';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Outlet } from '@tanstack/react-router';
import { MeshFilesProvider } from '@/contexts/MeshFilesContext';
import { PostHogProvider } from '@/contexts/PostHogProvider';
import { ErrorView } from '@/views/ErrorView';
import { isSupabaseConfigMissing } from '@/lib/supabase';
const queryClient = new QueryClient();
function MissingConfig() {
return (
<div className="flex min-h-screen items-center justify-center bg-adam-bg-secondary-dark">
<div className="max-w-xl px-4 text-center text-red-500">
Missing API Keys. Please copy .env.local.template to .env.local and
restart.
</div>
</div>
);
}
function App({ error }: { error?: unknown }) {
if (isSupabaseConfigMissing) {
return <MissingConfig />;
}
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<PostHogProvider>
<MeshFilesProvider>
<TooltipProvider delayDuration={0}>
<Toaster />
{error !== undefined ? <ErrorView error={error} /> : <Outlet />}
</TooltipProvider>
</MeshFilesProvider>
</PostHogProvider>
</AuthProvider>
</QueryClientProvider>
);
}
export default App;

Some files were not shown because too many files have changed in this diff Show More