Calculadora 3D Livre & Open-Source para precificação de impressões 3D. Free & open-source 3D printing cost calculator.
https://ils15.github.io/open3dcalc/ — Progressive Web App (PWA) com suporte offline, instalável como aplicativo nativo em qualquer navegador moderno.
- ✅ Offline-ready via service worker (Workbox)
- ✅ Instalável na tela inicial (add to homescreen)
- ✅ Auto-update em nova versão
- ✅ Responsivo (mobile-first)
Baixe a versão desktop para Windows ou Linux na página de releases.
| Plataforma | Formato | Arquivo |
|---|---|---|
| Windows (x64 / arm64) | NSIS Installer | Open3DCalc-{version}-setup.exe |
| Linux (x64 / arm64) | AppImage | Open3DCalc-{version}.AppImage |
⚠️ macOS build is configured but not actively published.
O Open3DCalc é local-first: seus dados vivem no seu dispositivo e nada é enviado a servidores. A partir da v1.12, a política de privacidade (LGPD) é executada pelo próprio aplicativo:
- Aba 🔒 Privacidade — um só lugar para ver e agir sobre seus dados:
- Quarentena de dados legados: dados antigos gravados em texto puro ficam legíveis, porém bloqueados para novas gravações, até você escolher migrar (criptografar e verificar) ou eliminar;
- Consentimento: um recibo à prova de adulteração, vinculado à versão exata da política que você aceitou — flags de tutorial/onboarding nunca substituem consentimento, e a retirada apaga os dados coletados sob ela;
- Apagar todos os meus dados: apagamento completo e verificável em todas as superfícies (banco, arquivos, caches, backups internos), com journal recuperável, snapshot criptografado de reversão (7 dias) e recibo listando as cópias externas que o app não alcança (ex.: exports salvos fora do app).
- Exportação sempre criptografada: o pacote de sincronização/exportação (
.open3dcalc) sai criptografado com AES-256-GCM a partir de uma senha sua — sem senha, não há export. Pacotes legados antigos continuam importáveis. - Backup bruto deixou de ser recurso de usuário: a cópia bruta do banco SQLite agora é um artefato de diagnóstico interno, bloqueado por padrão (gate de desenvolvimento), com modo de redação de dados pessoais e retenção máxima de 14 dias. Para levar seus dados a outra máquina, use o pacote de exportação criptografado.
Detalhes técnicos:
docs/privacy/(SPEC-01 manifest de dados, ADR-001 capacidade criptográfica, ADR-002 quarentena, ADR-003 export vs backup, SPEC-02 saga de apagamento, SPEC-03 envelope de exportação, SPEC-04 recibo de consentimento).
O motor de estimativa agora vai além do volume da malha — ele considera a configuração real da sua impressão e do seu filamento para calcular tempo, peso e custo.
- Perfil de fatiamento configurável (modo avançado): altura de camada, diâmetro do bico, velocidade e demais parâmetros de fatiamento agora alimentam a estimativa de tempo e material.
- Calibração de filamento (modo avançado): porcentagem de purge, diâmetro do filamento e velocidade volumétrica máxima (MVS) — com override para filamentos high-flow. A correspondência de perfis de filamento é case-insensitive.
- Fator geométrico: peças pequenas ou com muitos detalhes recebem um ajuste no tempo estimado (limitado a ±30%), pois exigem mais movimentos por unidade de volume.
- Transparência total: o painel "premissas usadas" mostra exatamente quais valores o estimador consumiu — sem caixa-preta.
- Geometria via G-code: quando o slicer não fornece metadata, as dimensões são extraídas dos movimentos do G-code; o perfil de fatiamento é auto-preenchido a partir do G-code sem sobrescrever sua customização.
- Validação de malha: aviso não-bloqueante quando a malha pode estar subestimando o volume (winding inconsistente, bordas abertas, geometria não-manifold ou triângulos degenerados). Malhas com mais de 1 milhão de triângulos usam validação parcial para não travar a interface.
Para além da estimativa de volume, o Open3DCalc calcula o custo real do seu dia a dia de impressão:
| Recurso | O que faz |
|---|---|
| 🧵 Restante do carretel | Cada carretel do inventário mostra o peso líquido restante (bruto menos a tara) e a metragem estimada, além de um indicador que diz se o carretel cobre a peça ativa. A tara é auto-preenchida por um banco de marcas embutido; você pode digitar a sua e, limpando o campo, a tara da marca volta a valer. |
| 💧 Resina lavável em água | Resinas water_washable são lavadas com água corrente — sem álcool isopropílico. Ao escolher uma, o meio de lavagem muda automaticamente para água e o custo de IPA da lavagem vai a zero no cálculo. O meio continua ajustável à mão no bloco "Lavagem e Cura". |
| 📊 Comparador de materiais | No painel de resultados, uma tabela colapsável mostra quanto a peça atual custaria em cada material FDM do catálogo, ordenável por custo — antes de comprar, você vê qual filamento sai mais barato. Resina não é comparável com FDM (processos diferentes) e a própria tabela explica o porquê. |
| 🖨️ Custo de máquina do catálogo | Ao selecionar uma impressora do catálogo, os custos de máquina da aba ativa são auto-preenchidos (valor, vida útil e manutenção). Como o catálogo guarda a manutenção em R$/hora, o app faz a conversão obrigatória para R$/mês a partir das suas horas de uso mensais. A derivação ocorre só na seleção — ajustar as horas mensais depois não recalcula. |
open3dcalc/
├── src/
│ ├── shared/ # Código compartilhado web + desktop
│ │ ├── components/ # Componentes React reutilizáveis
│ │ │ ├── Calculator/ # Calculadoras FDM e Resina
│ │ │ ├── Dashboard/ # KPIs, gráficos, projeções
│ │ │ ├── Catalog/ # Catálogo de impressoras, materiais
│ │ │ ├── StlPreview/ # Preview 3D (Three.js)
│ │ │ ├── Changelog/ # Changelog viewer
│ │ │ ├── Header/ # Navigation, theme toggle
│ │ │ └── ui/ # UI atoms (Button, Modal, Input, Table, etc.)
│ │ ├── stores/ # Zustand stores (estado global)
│ │ │ ├── calculatorStore.ts
│ │ │ ├── catalogStore.ts
│ │ │ ├── customerStore.ts
│ │ │ ├── historyStore.ts
│ │ │ ├── quoteStore.ts
│ │ │ ├── filamentInventory.ts
│ │ │ └── ...
│ │ ├── lib/ # Lógica de negócio
│ │ │ ├── calculator.ts # Núcleo do cálculo de custos
│ │ │ ├── stlParser.ts # Parsing STL/OBJ/3MF
│ │ │ ├── gcodeParser.ts # Parsing G-code
│ │ │ ├── pdfExport.tsx # Export PDF via @react-pdf/renderer
│ │ │ ├── csvExport.ts # Export CSV
│ │ │ ├── currency.ts # Conversão monetária
│ │ │ ├── printers.ts # Catálogo de 385+ impressoras
│ │ │ ├── materials.ts # 31 materiais pré-cadastrados
│ │ │ └── marketplace.ts # Taxas de marketplaces
│ │ ├── hooks/ # Custom hooks (useCurrency, useTheme, etc.)
│ │ ├── types/ # Tipos TypeScript compartilhados
│ │ ├── i18n/ # Traduções (pt-BR, en-US)
│ │ └── test/ # Test utilities & setup
│ └── platform/
│ ├── web/ # Código específico da PWA
│ │ ├── main.tsx # Entry point React (web)
│ │ └── App.tsx # Root component (web)
│ └── desktop/ # Código específico do Electron
│ ├── main.tsx # Entry point React (desktop)
│ ├── App.tsx # Root component (desktop)
│ └── overrides/ # Brides SQLite ↔ localStorage
│ ├── db-bridge.ts
│ ├── persistence-bridge.ts
│ ├── storage-adapter.ts
│ └── theme-persistence.ts
├── db/ # Database (SQLite via Drizzle ORM)
│ ├── schema/ # Schema definitions (Drizzle ORM)
│ │ ├── index.ts # 10 tabelas (customers, quotes, history, etc.)
│ │ └── relations.ts # Relacionamentos entre tabelas
│ ├── migrations/ # Migrations SQL (0000_initial, 0001_add_theme)
│ ├── database.ts # initDatabase() — singleton Drizzle instance
│ ├── seed.ts # Seed data (impressoras, materiais, marketplaces)
│ └── migrate.ts # Migration runner
├── electron/ # Electron main process (TypeScript)
│ ├── main.ts # Main process: window, IPC, DB init
│ └── preload.ts # Preload script (contextBridge)
├── web/ # Código legado (histórico git preservado)
├── desktop/ # Código legado desktop (histórico git preservado)
├── index.web.html # HTML entry point — web build
├── index.desktop.html # HTML entry point — desktop build
├── vite.base.config.ts # Config Vite base (compartilhada)
├── vite.web.config.ts # Config Vite — web
├── vite.desktop.config.ts # Config Vite — desktop
├── vitest.config.ts # Config Vitest
└── tsconfig.base.json # TypeScript base config
O preview 3D (StlPreview) aceita arrastar/soltar ou selecionar via explorador de arquivos:
| Formato | Extensão | Notas |
|---|---|---|
| STL | .stl |
binário e ASCII |
| OBJ | .obj |
Wavefront |
| 3MF | .3mf |
XML 3D Manufacturing |
| GCODE | .gcode, .gco, .g |
Cura (;TIME: em segundos) + PrusaSlicer/OrcaSlicer (; estimated printing time = 1h 23m 45s, suporta d/h/m/s combinados) — se o header de tempo não existir o arquivo ainda abre (tempo = —) e exibe dimensões/peso estimados pelo total de extrusão E, incluindo resets G92 e modo relativo M83 |
Troubleshooting GCODE (issue #32): se o tempo aparecer como
—, seu slicer não incluiu header de tempo ou usa formato não reconhecido — o arquivo continua sendo aceito (sem gate silencioso). Após carregar um GCODE a drop zone permanece visível e o botão 🗑️ (stl.clear) limpa o estado para novo upload sem dead-end.
- Node.js 22+ (recommended: 22 LTS)
- npm 10+
- Git
git clone https://github.com/ils15/open3dcalc.git
cd open3dcalc
npm installNote:
postinstallrunselectron-rebuildto compile nativebetter-sqlite3. It may take a few seconds.
# Web — servidor com hot-reload (http://localhost:5173)
npm run dev:web
# Desktop — Vite + Electron com hot-reload
npm run dev:desktop# Web — saída em dist-web/
npm run build:web
# Desktop — saída em dist/ + compila electron/
npm run build:desktop
npm run build:electron
# Ambos de uma vez
npm run build:all# Modo watch (desenvolvimento)
npm test
# Execução única (CI)
npm run test:run
# Com cobertura
npm run test:run -- --coverageWe use Vitest + Testing Library for unit and component tests. Minimum coverage for calculation logic: 80%.
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript 6, Tailwind CSS v4 |
| Build | Vite 8 |
| Desktop | Electron 42, better-sqlite3, Drizzle ORM |
| Web (PWA) | vite-plugin-pwa (Workbox service worker) |
| State Management | Zustand 5 |
| Testing | Vitest 4, Testing Library (React + Jest DOM) |
| i18n | i18next 26, react-i18next 17 |
| Charts | Recharts 2 |
| 3D Preview | Three.js + React Three Fiber + Drei |
| PDF Export | @react-pdf/renderer 4 |
| Animations | Framer Motion 12 |
| Icons | Lucide React |
| Linting | ESLint 10, TypeScript ESLint, Prettier 3 |
| CI/CD | GitHub Actions |
| Commit Lint | commitlint + husky + lint-staged |
| Changelog | changelogen |
- Engine: SQLite via
better-sqlite3(síncrono, embarcado) - ORM: Drizzle ORM — schema definido em
db/schema/index.ts - Migrations: SQL puro em
db/migrations/(gerados viadrizzle-kit) - Tables: customers, quotes, quote_items, history_entries, filament_spools, catalog_printers, catalog_materials, catalog_marketplaces, calculator_state, app_settings, storage
- Storage Bridge: A camada de persistência do desktop substitui o
localStorageda web pelo SQLite via adaptador IPC (src/platform/desktop/overrides/storage-adapter.ts) - Seed:
db/seed.tspovoa os catálogos de impressoras (385+), materiais (31) e marketplaces (6)
# Gerar nova migration após alterar schema
npm run db:generate
# Executar migrations pendentes
npm run db:migrateWeb: No SQLite. All persistence is via
localStorage(browser).
| Script | Description |
|---|---|
npm run dev:web |
Start web dev server (Vite, hot-reload) |
npm run dev:desktop |
Start Electron + Vite dev (hot-reload) |
npm run dev:electron |
Compile + launch Electron main process |
npm run build:web |
Build web app → dist-web/ |
npm run build:desktop |
Build desktop renderer → dist/ |
npm run build:electron |
Compile Electron main process (TypeScript) |
npm run build:all |
Build both web + desktop |
npm run build:shared |
TypeScript check shared code (--noEmit) |
npm run preview:web |
Preview web production build locally |
npm test |
Run tests in watch mode |
npm run test:run |
Run tests once (CI mode) |
npm run lint |
ESLint check across entire project |
npm run typecheck |
TypeScript check (tsc --noEmit -p tsconfig.app.json) |
npm run typecheck:electron |
TypeScript check for Electron main process |
npm run db:generate |
Generate Drizzle ORM migrations |
npm run db:migrate |
Run pending SQLite migrations |
npm run postinstall |
Rebuild native modules (electron-rebuild) |
No .env file is required. All app config is persisted via:
- Web:
localStorage(browser) - Desktop: SQLite via
better-sqlite3+ persistence adapter
Optional environment variables:
| Variable | Values | Purpose | Default |
|---|---|---|---|
OPEN3DCALC_DB_PATH |
path string | Custom path to SQLite file (tests/CLI) | — |
VITE_TOOLPATH_PREVIEW |
true / false |
Enable the 3D G-code toolpath preview | true (validated in the beta channel; set false to disable) |
VITE_BETA_CHANNEL |
true / false |
Selo visual de beta no app web | false |
O deploy da web é automático via GitHub Actions (ci-cd.yml) a cada tag estável imutável (vX.Y.Z):
- CI roda lint, typecheck, testes e build na tag
- O web build é publicado na branch
gh-pages(raiz) viapeaceiris/actions-gh-pages - Os arquivos
404.htmleindex.htmlsão gerados para roteamento SPA
A branch
gh-pagesé branch-based (não artifact-based) justamente para que o canal beta possa viver no subpath/beta/sem clobberar a raiz estável. Veja a mudança da fonte do Pages em Canal Beta abaixo.
O canal beta publica builds web-only (Electron nunca é buildado) num subpath isolado do GitHub Pages, permitindo validar mudanças antes de promover a estável.
🐕 Dogfood: o preview 3D do G-code (D-CL5/D-CL6) foi dogfoodado no canal beta (
v1.13.0-beta.1..3, 3 betas / 1933 testes / paridade do oráculo legado validada) e aprovado para a release estável — agora está ligado por padrão em todos os builds (VITE_TOOLPATH_PREVIEW=falseainda desliga; veja a tabela de env vars acima). O canal beta segue como o canal de dogfood das próximas novidades.
🧪 Exemplos embutidos: a beta traz o 3DBenchy (CreativeTools, domínio público / CC0 — livre pra redistribuir) na drop zone do visualizador. Dois botões baixam o exemplo sob demanda, sem precisar do seu próprio arquivo: Benchy (STL) dispara o pipeline de malha (volume + peso) e Benchy (G-code) dispara o preview de toolpath com o slider de camadas. Os binários vivem em
public/samples/e a URL é resolvida relativa ao deploy (funciona na raiz e no subpath/beta/).
| Estável | Beta | |
|---|---|---|
| URL | https://ils15.github.io/open3dcalc/ |
https://ils15.github.io/open3dcalc/beta/ |
| Versão | vX.Y.Z |
vX.Y.Z-beta.N |
| Origem | tag estáável (ci-cd.yml) |
tag beta (beta-deploy.yml) |
| Build | web + desktop | web-only |
| Changelog | CHANGELOG.md + GitHub Release |
somente no corpo da GitHub Release |
Cortando uma beta
- Vá em Actions → Beta channel → Run workflow
- O workflow (
beta.yml):- Calcula a próxima versão (auto: próximo minor da versão atual; ou a base informada no input)
- Bumpa
package.json, faz commit e cria a tag anotada imutávelvX.Y.Z-beta.N - Empurra commit + tag com o PAT
BETA_RELEASE_TOKEN
- A tag dispara o
beta-deploy.yml, que:- Builda a web com
VITE_BETA_CHANNEL=true(selo visual de beta) - Publica em
gh-pages/beta/sem tocar na raiz estáável (keep_files: true) - Cria (ou atualiza) a GitHub Release prerelease
Beta vX.Y.Z-beta.N
- Builda a web com
As tags beta são imutáveis: nunca reescreva ou delete uma tag já publicada — corte uma nova beta (beta.N+1) caso precise ajustar algo. O beta-deploy.yml é idempotente, então re-executá-lo na mesma tag apenas refresca a release.
O changelog do beta existe somente no corpo da GitHub Release — CHANGELOG.md e o changelog in-app nunca carregam betas, pois o parser de scripts/sync-changelog.mjs colidiria em chaves como 1.13.0 vs 1.13.0-beta.1.
Promover beta → estável: o fluxo normal de release (release.yml) consolida todos os commits desde a última tag estável, então o changelog da release estável já inclui todo o período das betas. Veja RELEASE.md.
🔑
BETA_RELEASE_TOKEN(obrigatório)O GitHub suprime novas execuções de workflow causadas pelo
GITHUB_TOKEN(anti-recursão). A tag beta precisa ser empurrada por um Personal Access Token (classic) com escopocontents: write; caso contrário a tag é criada, mas obeta-deploy.ymlnunca dispara.Como configurar:
- Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token
- Escopo:
repo(ou no mínimocontents: write); o seloworkflownão é necessário- Settings → Secrets and variables → Actions → New repository secret → nome
BETA_RELEASE_TOKEN, valor = tokenTanto
beta.ymlquantobeta-deploy.ymlfazem fail-fast logo no início se o secret estiver vazio, explicando o problema no log.
Mudança da fonte do Pages (cutover, já concluído)
Como o beta vive em gh-pages/beta/ e a raiz de gh-pages é a estável, a fonte do GitHub Pages foi mudada de GitHub Actions para a branch gh-pages: Settings → Pages → Build and deployment → Source: Deploy from a branch → branch gh-pages / pasta **/ (root)**.
✅ One-off concluído: o workflow
.github/workflows/seed-gh-pages.ymlpopulou a raiz degh-pagescom um build estável durante o cutover (sem apagar/beta/, run35358169749) e foi removido na sequência — existia apenas para esse cutover pontual. Hoje a raiz é mantida peloci-cd.yml(build-web) a cada tag estável.
O release é um processo de duas fases (preparação + publicação), detalhado em RELEASE.md.
Preparação (GitHub Actions):
- Em Actions → Release preparation → Run workflow, selecione
maine o bump desejado - O workflow cria
release/vX.Y.Z, atualiza versão com changelogen 0.6.2, sincroniza o changelog in-app, roda lint/typecheck/testes/build e abre um PR paramain - Revise e faça o merge do PR
Publicação (tag local):
git fetch origin main && git switch main && git pull --ff-only origin main
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.ZO workflow Release publication valida a tag, cria a GitHub Release com nome Open3DCalc vX.Y.Z e anexa os artefatos Windows/Linux. A branch release/vX.Y.Z é removida após confirmação.
As notas de cada GitHub Release usam um formato canônico renderizado por scripts/release-notes.mjs (renderPublication()) e validado por um gate fail-closed antes da publicação:
npm run release:notes:validate -- --file release-notes.md --tag vX.Y.ZO workflow Release publication gera o corpo com --notes-file (em vez de --generate-notes) e só publica se o validador aprovar. As seções emoji têm ordem fixa: 🚀 Features, 🐛 Fixes, 🧹 Chores, 📦 Dependencies, 🤖 CI/CD e ❤️ Contributors (além de 📚 Documentation, 🔒 Security e
O backfill de releases antigas é idempotente e reversível por snapshot local (release-notes-snapshots/):
node scripts/backfill-release-notes.mjs --dry-run --all
node scripts/backfill-release-notes.mjs --apply --all
node scripts/backfill-release-notes.mjs --restore vX.Y.ZContributions are welcome! See the full guide at CONTRIBUTING.md.
Workflow summary:
- Fork the repository
- Create a branch (
feature/,fix/,docs/, etc.) - Commit following Conventional Commits
- Run
npm run lint,npm run typecheck,npm run test:run,npm run build:all - Open a Pull Request (minimum 1 approval)
See CHANGELOG.md for the full version history.
Este projeto utiliza um self-hosted runner CX33 para execução dos pipelines de CI/CD:
- 🚀 Zero custo de execução (vs GitHub Actions hosted)
- 💤 Runner sleep quando ocioso — zero consumo
- 🔥 Acorda automaticamente nos pushes/PRs
- 🔒 Segredos e cache locais (sem egress)
MIT License — see LICENSE for details.
- Live Demo: https://ils15.github.io/open3dcalc/
- Repository: https://github.com/ils15/open3dcalc
- Issues: https://github.com/ils15/open3dcalc/issues
- Releases: https://github.com/ils15/open3dcalc/releases
- Telegram Community: Impressão 3D BR