diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..307763a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: Bug report +description: Report a reproducible modeling, persistence, rendering, or export defect. +title: "[Bug]: " +labels: [bug] +body: + - type: markdown + attributes: + value: Do not attach confidential project files or client information to a public issue. + - type: input + id: build + attributes: + label: FutolStructure build + placeholder: v3.16.115 + validations: + required: true + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Project save/load or recovery + - Plan geometry or annotations + - Tributary loads or schedules + - 3D rendering + - DXF export + - IFC export + - STAAD export + - ETABS or SAFE handoff + - Other + validations: + required: true + - type: textarea + id: steps + attributes: + label: Reproduction steps + description: Use a minimal non-confidential model when possible. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected and actual result + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Screenshots or diagnostics + description: Remove names, addresses, credentials, and other private data. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f86f91c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/michaelfutol/futolstructure/security/advisories/new + about: Report security issues privately. Do not disclose them in a public issue. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4f6a248 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Summary + +Describe the engineering behavior changed and why. + +## Validation + +- [ ] `node v3/tools/check-fs.js --no-browser` +- [ ] Full browser smoke completed when geometry, persistence, 3D, or exports changed +- [ ] No private project, solver, credential, or client data is included +- [ ] Claims and warnings match the validated scope + +## Evidence + +List the model, counts, screenshots, or solver audit used to verify the change. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..4964453 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,35 @@ +name: Validate FutolStructure + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: validate-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + name: Static and engine smoke + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Validate application source and engine + run: node v3/tools/check-fs.js --no-browser + + - name: Validate deployment configuration + run: | + node -e "JSON.parse(require('fs').readFileSync('vercel.json', 'utf8')); console.log('vercel.json: valid')" diff --git a/.gitignore b/.gitignore index b3dae61..48b5413 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,25 @@ node_modules/ .vscode/ *.swp *.swo +.vercel/ # Temporary files *.tmp *.temp + +# FutolStructure private project and solver/export artifacts +*.fstr +*.edb +*.e2k +*.std +*.sdb +*.ifc +*.dxf +*.xlsx +output/ +tmp/ + +# Local personal/recovery notes +kira_diary/ + +.vercel diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..b43a5e6 --- /dev/null +++ b/.vercelignore @@ -0,0 +1,45 @@ +# Development and recovery files not needed by the hosted static app +.git +.gitignore +.github/ +.vercel +.env +.env.* +docs/ +README.md +CONTRIBUTING.md +CODE_OF_CONDUCT.md +SECURITY.md +LICENSE +kira_diary/ +output/ +tmp/ + +# Local scripts and generated repair tools +*.py +inject_*.py +repair_*.py +upgrade_*.py +fix_rendering.py + +# Internal logs/tests +v3/_logs/ +v3/tools/ + +# Private engineering/project artifacts +*.fstr +*.edb +*.e2k +*.std +*.sdb +*.ifc +*.dxf +*.xlsx +*.pdf +*.docx + +# Local OS/editor noise +.vscode/ +.idea/ +Thumbs.db +.DS_Store diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c53d57f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Code of Conduct + +## Our Standard + +Contributors and maintainers are expected to communicate professionally, +respect differing levels of experience, and keep technical disagreement +focused on evidence, safety, and project outcomes. + +Unacceptable behavior includes harassment, discriminatory language, personal +attacks, deliberate disclosure of private project data, and publication of +security details before coordinated remediation. + +## Engineering Discussions + +Structural-engineering claims should identify assumptions, governing scope, +and validation evidence. Review comments may be direct, but must remain +specific, respectful, and actionable. Safety-critical concerns take priority +over schedule or presentation. + +## Enforcement + +Report conduct or security concerns through the repository's private +vulnerability-reporting channel. Maintainers may edit, remove, or reject +contributions and interactions that violate this policy. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..998274c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to FutolStructure + +FutolStructure accepts focused bug reports and carefully scoped pull requests. +Before proposing code, open an issue that explains the engineering behavior, +the affected model or export surface, and the expected result. + +## Development Rules + +- Preserve `.fstr` backward compatibility or document and test a migration. +- Use the active floor-aware geometry and member-state helpers; do not revive + legacy global flags as a second source of truth. +- Keep analysis, schedules, 3D geometry, and exports coordinated from the same + active-model payload. +- Do not describe preliminary calculations as final design or solver truth. +- Do not commit project files, native solver files, credentials, client data, + or generated validation artifacts. +- Keep unrelated formatting and refactoring out of behavioral fixes. + +## Validation + +Run the local smoke check before opening a pull request: + +```bash +node v3/tools/check-fs.js --no-browser +``` + +For geometry, persistence, rendering, or export changes, also run: + +```bash +node v3/tools/check-fs.js +``` + +Describe the tested model, affected counts, and any validation boundary that +remains unverified. Pull requests must pass the repository validation workflow. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..245bccb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +FutolStructure Source License + +Copyright (c) 2026 Michael Futol. All rights reserved. + +The source code, documentation, visual identity, and other materials in this +repository are made publicly viewable for evaluation and portfolio purposes. +No permission is granted to copy, modify, distribute, sublicense, sell, host, +or create derivative works from these materials without prior written +permission from the copyright owner. + +This software is provided "AS IS", without warranty of any kind, express or +implied, including but not limited to warranties of merchantability, fitness +for a particular purpose, accuracy, code compliance, structural adequacy, or +noninfringement. In no event shall the copyright owner or contributors be +liable for any claim, damages, or other liability arising from use of the +software or its outputs. + +FutolStructure is an engineering decision-support and model-preparation tool. +Its output must be reviewed, completed, and accepted by the responsible +licensed engineer before it is used for construction, permitting, or safety- +critical decisions. diff --git a/README.md b/README.md index 366aa8c..4d4c2d4 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,146 @@

- FutolStructure logo + FutolStructure logo

-# FutolStructure v3.10 +# FutolStructure -[Download the FutolStructure app icon](v3/assets/futolstructure-icon.png) +FutolStructure is a browser-based structural engineering workbench for reinforced-concrete building layout, gravity load-path review, tributary area visualization, 3D coordination, and solver handoff preparation. -> Professional structural engineering web application for reinforced concrete buildings with tributary area analysis, 3D visualization, and load distribution calculations. +

+ Open the live technical preview +  |  + Deployment +  |  + Security +

+ +[![Build](https://img.shields.io/badge/build-v3.16.115-2563eb)](https://futolstructure.vercel.app) +[![Validation](https://github.com/michaelfutol/futolstructure/actions/workflows/validate.yml/badge.svg)](https://github.com/michaelfutol/futolstructure/actions/workflows/validate.yml) +![Platform](https://img.shields.io/badge/platform-browser-0f766e) +![Maturity](https://img.shields.io/badge/maturity-technical%20preview-f59e0b) +![License](https://img.shields.io/badge/license-all%20rights%20reserved-475569) + +> [!IMPORTANT] +> FutolStructure is an engineering decision-support and model-preparation tool. It does not replace project-specific analysis, code checks, geotechnical input, detailing, or review and signing by the responsible licensed engineer. + +## Product Workflow + +```mermaid +flowchart LR + A[Grid and floor intent] --> B[Active structural geometry] + B --> C[Tributary and gravity load path] + C --> D[Plans, schedules, and 3D review] + D --> E[DXF, IFC, STAAD, and ETABS handoff] +``` + +## Screenshots + +### Plan, tributary, and load review + +![FutolStructure plan and tributary view](v3/assets/screenshots/futolstructure-plan.png) + +### 3D structural model + +![FutolStructure 3D structural model](v3/assets/screenshots/futolstructure-3d.png) + +### Stair Builder -![License](https://img.shields.io/badge/license-MIT-blue.svg) -![Version](https://img.shields.io/badge/version-3.10-green.svg) -![Platform](https://img.shields.io/badge/platform-Web-orange.svg) +![FutolStructure Stair Builder](v3/assets/screenshots/futolstructure-stair-builder.png) -## 🏗️ Features +## Current Capabilities -### Core Analysis -- **Two-way slab tributary distribution** (45° rule with trapezoidal/triangular loads) -- **Complete load path:** Slab → Beams → Columns → Footings -- **Multi-floor stacking** with per-floor configurations -- **Factored load calculations** (NSCP/ACI 318 compliant) +- Grid-based RC framing with columns, beams, slabs, cantilevers, corner slab patches, and floor-specific controls. +- Tributary area and gravity load distribution from slabs to beams, columns, footings, and base reactions. +- Plan drafting aids including coordinated member tags, grid bubbles, dimensions, ortho measurement, snapping, and lock indicators. +- 3D review with display schemes, member colors and opacity controls, foundation geometry, slab transparency, and stair geometry. +- `.fstr` project save/load with guarded autosave, recovery diagnostics, floor deletion warnings, and persisted member locks. +- Stair Builder geometry with destination slab openings, DXF footprint output, and 3D review. +- Reports and schedules for columns, beams, slabs, footings/base reactions, and preliminary design summaries. +- Coordinated DXF, IFC2x3, STAAD.Pro, ETABS 22 OAPI, and ETABS-to-SAFE handoff paths. -### 3D Visualization -- Full **3D BIM-style view** with Three.js -- Color-coded members (columns, beams, slabs, footings) -- **Member labels** (C1-A1, B-X-A1, etc.) -- **Ground plane with gridline bubbles** -- Interactive orbit controls +## Solver and BIM Status -### Engineering Tools -- **Footing design** with soil bearing capacity -- **Tie beam sizing** (span/10 rule) -- **Column sizing** (NSCP minimum requirements) -- Per-floor cantilever support -- Custom beam placement -- Void slab toggles +FutolStructure uses a shared active-model payload so geometry counts and member intent remain coordinated across export targets. -### User Experience -- **Undo/Redo** (Ctrl+Z / Ctrl+Y) - 10 command history -- Dark/Light theme support -- Export to JSON -- Keyboard shortcuts -- Responsive design +| Target | Current status | +| --- | --- | +| DXF | Structural plan output and governed layer mapping are active. | +| IFC2x3 | Active columns, beams, slabs, and storey organization are exported for BIM review. | +| STAAD.Pro | The gravity baseline, frame/plate geometry, beam insertion offsets, and statics balance were validated in STAAD.Pro 2024. | +| ETABS 22 | The OAPI builder creates a dated working copy, assigns the governed mass baseline, runs modal analysis, and exports audit artifacts. | +| SAFE | The supported handoff remains ETABS story export plus governed base reactions; direct browser-authored SAFE models are not claimed. | -## 🚀 Quick Start +Validated baselines do not imply final lateral design, detailing compliance, or permit readiness for an arbitrary project. The current reliable envelope is regular orthogonal low-rise RC framing; irregular and transfer behavior requires explicit solver review. -1. Clone or download this repository -2. Open `v3/index.html` in any modern browser -3. No build step required - works offline! +## Quick Start + +No build step is required for the current static application. ```bash git clone https://github.com/michaelfutol/futolstructure.git cd futolstructure -# Open v3/index.html in browser +python -m http.server 4173 ``` -## 📁 Project Structure +Open `http://127.0.0.1:4173/v3/index.html`. + +## Validation + +Run the syntax and engine smoke check: +```bash +node v3/tools/check-fs.js --no-browser ``` + +Run the full browser smoke check with Chrome or Edge installed: + +```bash +node v3/tools/check-fs.js +``` + +The browser smoke covers initialization, plan geometry, slab ownership, cantilever behavior, persistence and recovery guards, member locking, measurement tools, stair persistence, 3D rendering, and export payload parity. + +## Repository Layout + +```text futolstructure/ -├── v3/ -│ ├── index.html # Main application (single-file) -│ ├── _logs/ # Development logs -│ └── _archive/ # Previous versions -├── README.md -└── .gitignore +|-- .github/ Release checks and contribution templates +|-- docs/ Launch, security architecture, and release notes +|-- v3/ +| |-- assets/ Product identity and screenshots +| |-- engine/ Extracted structural calculation helpers +| |-- tools/ Regression smoke runner +| `-- index.html Current application +|-- index.html Hosted root entry +|-- vercel.json Static deployment and security headers +`-- README.md ``` -## 🛠️ Tech Stack +## Data and Security Boundary -- **Frontend:** Pure HTML/CSS/JavaScript -- **3D Engine:** Three.js (CDN) -- **Dependencies:** None (works offline) -- **Size:** ~565KB single file +- The public technical preview is local-file first and does not yet provide user accounts or cloud project storage. +- `.fstr` model data stays in the browser or in files explicitly saved by the user. +- Experimental client-side AI key panels are disabled for the public preview. Future AI or collaboration features must use an authenticated server boundary and explicit project-data consent. +- Private project files and native solver artifacts are excluded from source control and Vercel deployments. -## 📋 Keyboard Shortcuts +See [SECURITY.md](SECURITY.md) for vulnerability reporting and [docs/AUTH_SECURITY_PLAN.md](docs/AUTH_SECURITY_PLAN.md) for the planned Supabase authentication and row-level-security architecture. -| Shortcut | Action | -|----------|--------| -| `Ctrl+Z` | Undo | -| `Ctrl+Y` | Redo | -| `Scroll` | Zoom | -| `Drag` | Pan (in 2D view) | +## Deployment -## 🎯 Roadmap +- Production preview: https://futolstructure.vercel.app +- Custom domain target: https://futolstructure.futoltech.com +- Release workflow: pull request preview, automated validation, merge to `main`, then Vercel production deployment. -- [ ] Rebar calculation for footings -- [ ] Punching shear checks for flat slabs -- [ ] Export to AutoCAD DXF -- [ ] PDF report generation +The custom domain requires its DNS record before it becomes public. See [docs/LAUNCH_PLAN.md](docs/LAUNCH_PLAN.md) for the exact record and verification command. -## 📄 License +## Project Status -MIT License - Free to use for personal and commercial projects. +The product is under active technical validation. Near-term work focuses on protected `.fstr` revisions, solver export truth, authenticated cloud projects, stair analytical connectivity, and continued BIM round-trip testing. -## 👨‍💻 Author +Contributions should preserve engineering traceability and avoid claims beyond validated behavior; read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. -**FutolTech** - Engineering & Project Systems +## Author ---- +FutolTech - Engineering & Project Systems -*Built with ❤️ for structural engineers* +Copyright (c) 2026 Michael Futol. All rights reserved. See [LICENSE](LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1dbf5d0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +## Supported Version + +Security fixes are applied to the latest code on the default branch and the +current production deployment. Historical archives and generated solver files +are not supported runtime releases. + +## Reporting a Vulnerability + +Please use GitHub private vulnerability reporting for this repository: + +https://github.com/michaelfutol/futolstructure/security/advisories/new + +Do not disclose API keys, private `.fstr` project files, solver models, client +names, or exploit details in a public issue. If private reporting is +temporarily unavailable, open a public issue containing only a request for a +private contact channel. + +Include the affected URL or revision, reproduction steps, expected impact, +and any safe proof-of-concept material. Reports will be acknowledged as soon +as practical and triaged according to impact and reproducibility. + +## Current Data Boundary + +The public technical preview is a static, local-file-first application. It has +no production user database or cloud project store. Experimental client-side +AI key panels are disabled in the public release. Authentication and cloud +storage must not be enabled until server-side authorization and row-level +database policies are in place. diff --git a/docs/AUTH_SECURITY_PLAN.md b/docs/AUTH_SECURITY_PLAN.md new file mode 100644 index 0000000..034b082 --- /dev/null +++ b/docs/AUTH_SECURITY_PLAN.md @@ -0,0 +1,151 @@ +# FutolStructure Auth And Security Plan + +This is the recommended path for adding user accounts, project storage, and secure collaboration without weakening the current engineering workflow. + +## Recommendation + +Use Vercel for hosting and Supabase for authentication, Postgres storage, and row-level security. + +Do not implement a client-only password screen as the final security model. Client-side checks can improve user experience, but they cannot protect project data because browser JavaScript can be inspected and bypassed. + +## Product Stages + +### Stage 1 - Public portfolio/demo + +Goal: publish FutolStructure as a static browser app at: + +```text +https://futolstructure.futoltech.com +``` + +Scope: + +- No private project files hosted by default. +- Users can still open/save local `.fstr` files. +- README and screenshots present the product professionally. +- Vercel auto-deploys from the selected GitHub branch. + +### Stage 2 - Accounts and cloud projects + +Goal: authenticated users can save projects to the cloud. + +Recommended stack: + +- Supabase Auth for email/password and optional OAuth. +- Supabase Postgres for project metadata. +- Supabase Storage or Postgres JSONB for `.fstr` payloads. +- Row-level security so users only access projects they own or are invited to. +- Vercel environment variables for Supabase URL and public anon key. + +Roles: + +| Role | Access | +| --- | --- | +| owner | Full project access, billing/export ownership, sharing controls. | +| engineer | Edit model, run reports, export solver files. | +| reviewer | Read-only model/report access. | +| admin | Manage users and organization settings. | + +### Stage 3 - Organization workspace + +Goal: FutolTech can host teams, projects, and review workflows. + +Features: + +- Organization/team membership. +- Project folders and timeline history. +- Saved revisions with named checkpoints. +- Audit log for save/load/export events. +- Optional public demo mode separate from authenticated workspaces. + +## Suggested Database Shape + +```sql +profiles ( + id uuid primary key references auth.users(id), + display_name text, + created_at timestamptz default now() +) + +organizations ( + id uuid primary key default gen_random_uuid(), + name text not null, + owner_id uuid references auth.users(id), + created_at timestamptz default now() +) + +organization_members ( + organization_id uuid references organizations(id), + user_id uuid references auth.users(id), + role text check (role in ('owner', 'admin', 'engineer', 'reviewer')), + primary key (organization_id, user_id) +) + +projects ( + id uuid primary key default gen_random_uuid(), + organization_id uuid references organizations(id), + owner_id uuid references auth.users(id), + name text not null, + current_revision_id uuid, + created_at timestamptz default now(), + updated_at timestamptz default now() +) + +project_revisions ( + id uuid primary key default gen_random_uuid(), + project_id uuid references projects(id), + created_by uuid references auth.users(id), + label text, + fstr_json jsonb not null, + app_build text, + created_at timestamptz default now() +) +``` + +## Security Rules + +- Enable row-level security on every table. +- Users can read projects only when they own the project or belong to the project organization. +- Only owners/admins/engineers can create project revisions. +- Reviewers get read-only access. +- Never store service-role keys in browser code. +- Keep Supabase service-role keys only in Vercel serverless functions if needed. +- Treat exported solver files as private project artifacts. + +## App Refactor Needed + +The current app is a static single-file workbench. That is good for fast launch, but cloud auth will be cleaner after one of these refactors: + +| Option | Pros | Tradeoff | +| --- | --- | --- | +| Keep static app + Supabase client | Fastest login/cloud-save path. | More care needed to keep all access controlled by RLS. | +| Move to Vite app | Better modules, still simple static deployment. | Needs bundling step. | +| Move to Next.js on Vercel | Best for server-side auth, API routes, team features, and protected exports. | Larger migration. | + +Recommended sequence: + +1. Launch static portfolio/demo first. +2. Add pre-save backups and project revision safety locally. +3. Convert persistence layer to an internal `ProjectStore` interface. +4. Implement `LocalFileProjectStore` and `SupabaseProjectStore`. +5. Add login UI only after row-level security is active. + +## Environment Variables + +For Supabase: + +```text +NEXT_PUBLIC_SUPABASE_URL +NEXT_PUBLIC_SUPABASE_ANON_KEY +SUPABASE_SERVICE_ROLE_KEY +``` + +If the app remains static, only the public URL and anon key should be exposed. Service-role access requires serverless functions and must never ship to the browser. + +## Definition Of Done + +- Unauthenticated users cannot read cloud projects. +- A user cannot read or edit another user's project by changing IDs in the browser. +- Every cloud save creates a revision instead of silently overwriting the only copy. +- The app can export/download a local `.fstr` backup even when cloud save fails. +- Login/logout does not affect local-only project workflows. diff --git a/docs/LAUNCH_PLAN.md b/docs/LAUNCH_PLAN.md new file mode 100644 index 0000000..96ae60a --- /dev/null +++ b/docs/LAUNCH_PLAN.md @@ -0,0 +1,169 @@ +# FutolStructure Launch Plan + +This is the working launch path for publishing FutolStructure at: + +```text +futolstructure.futoltech.com +``` + +## Repository Decision + +Use the existing GitHub repository first: + +```text +https://github.com/michaelfutol/futolstructure +``` + +Do not create a second repository yet. A new repository is only useful if we later want a fully clean public history with no legacy development artifacts. For now, the faster and safer route is: + +1. Keep the existing repo as the source of truth. +2. Stage only intentional public-facing changes. +3. Push a stable branch. +4. Merge reviewed release changes to `main`, which is the Vercel production branch. + +## Local Folder Decision + +Do not delete old local files while active development is ongoing. The current folder contains recovery scripts, old upgrade scripts, and logs that may still help diagnose model-history issues. + +For public presentation, use one of these approaches: + +| Approach | Use when | Notes | +| --- | --- | --- | +| Same repo, clean README | Fastest launch | Good for private/portfolio review now. | +| `release/web` branch | Public demo branch | Stage only `README.md`, `index.html`, `v3/`, `docs/`, and deploy config. | +| New clean repo | Later portfolio polish | Use after the app stabilizes and we want a smaller public history. | + +Current decision: keep `main` as the reviewed production branch. Pull requests receive Vercel previews and merge to production only after validation passes. + +## Deployment Options + +### Option A - Vercel + +Recommended for fast preview links, GitHub integration, and easy custom-domain setup. + +Current Vercel project: + +```text +scope: ikel-eidras-projects +project: futolstructure +production: https://futolstructure.vercel.app +production branch: main +validated deployment: dpl_6BPVUFHxE7NqPEUSdQXCmUXkziiB +custom domain target: futolstructure.futoltech.com +``` + +1. Push the selected branch to GitHub. +2. Import `michaelfutol/futolstructure` into Vercel. +3. Framework preset: Other. +4. Build command: leave blank. +5. Output directory: leave blank. +6. Add custom domain: + +```text +futolstructure.futoltech.com +``` + +7. In DNS, point the subdomain to the Vercel target shown in the project domain settings. + +Current DNS action required, as reported by Vercel on 2026-07-13: + +```text +type: A +name/host: futolstructure +value: 76.76.21.21 +``` + +Create this record at the DNS provider currently serving `futoltech.com`. Do not replace the domain's nameservers merely for this subdomain. After changing DNS, verify with: + +```bash +npx --yes vercel@latest domains verify futolstructure.futoltech.com +``` + +The included `vercel.json` rewrites the domain root to: + +```text +/v3/index.html +``` + +After GitHub integration is connected, Vercel will redeploy automatically: + +- Pushes to the production branch update the live production site. +- Pull requests and non-production branches get preview URLs. +- Rollback can be done from the Vercel dashboard if a release is bad. + +### Option B - cPanel or traditional hosting + +Use this if `futoltech.com` is already hosted on cPanel and we want direct control. + +1. Create the subdomain in cPanel: + +```text +futolstructure.futoltech.com +``` + +2. Upload these repo contents to that subdomain document root: + +```text +index.html +v3/ +README.md +docs/ +``` + +3. Confirm this opens: + +```text +https://futolstructure.futoltech.com/v3/index.html +``` + +4. Confirm the root redirects: + +```text +https://futolstructure.futoltech.com +``` + +## Pre-Launch Checklist + +- [x] README uses the FutolStructure name and current build. +- [x] Screenshots are stored under `v3/assets/screenshots/`. +- [x] Root `index.html` no longer references old `v3.10` wording. +- [x] Repository license, contribution, security, issue, and pull-request policies exist. +- [x] GitHub validation workflow runs the source/engine smoke check. +- [ ] Full browser smoke passes for the release commit. +- [x] No private `.fstr` project files are committed. +- [x] No personal native-solver project files are committed. +- [x] Public README does not claim permit-ready design without engineer review. +- [ ] DNS resolves `futolstructure.futoltech.com` to Vercel. + +## Recommended Git Staging + +For a portfolio update, stage only the intentional public and launch files: + +```bash +git add README.md LICENSE SECURITY.md CONTRIBUTING.md CODE_OF_CONDUCT.md +git add .github/ index.html robots.txt sitemap.xml vercel.json docs/LAUNCH_PLAN.md +git add v3/index.html v3/assets/screenshots/ v3/tools/check-fs.js +``` + +Review before commit: + +```bash +git diff --cached --stat +git diff --cached -- README.md index.html docs/LAUNCH_PLAN.md vercel.json +``` + +Suggested commit message: + +```text +Prepare FutolStructure portfolio launch +``` + +## Next Product Goals + +1. Add pre-save backups and save health gates for `.fstr` protection. +2. Complete custom-domain DNS and switch canonical metadata to the verified custom domain. +3. Add accounts and cloud project storage using the architecture in `docs/AUTH_SECURITY_PLAN.md`. +4. Add a short demo video or GIF to the README. +5. Continue export validation for ETABS, STAAD, IFC, SAFE, and Revit review. +6. Improve stair export from visual geometry to solver-ready shell/frame connectivity. +7. Replace client-only experimental AI with an authenticated, consent-based server integration if it returns. diff --git a/docs/RELEASE_HYGIENE.md b/docs/RELEASE_HYGIENE.md new file mode 100644 index 0000000..7cdddf4 --- /dev/null +++ b/docs/RELEASE_HYGIENE.md @@ -0,0 +1,59 @@ +# Release Hygiene + +Use this checklist before pushing a public FutolStructure update. + +## Do Not Commit + +- Private `.fstr` client/project files. +- Solver output folders from ETABS, STAAD.Pro, SAFE, SAP2000, or Revit. +- Local screenshots outside `v3/assets/screenshots/`. +- Temporary browser profiles, cache folders, and generated debug output. +- Personal notes unless intentionally public. + +## Keep In The Public Repo + +- `README.md` +- `LICENSE`, `SECURITY.md`, `CONTRIBUTING.md`, and `CODE_OF_CONDUCT.md` +- `.github/` validation and contribution templates +- `index.html` +- `v3/index.html` +- `v3/assets/futolstructure-icon.png` +- `v3/assets/screenshots/` +- `v3/engine/` +- `v3/tools/check-fs.js` +- `docs/` +- deployment config such as `vercel.json` + +## Before Each Commit + +```bash +git status --short +node v3/tools/check-fs.js --no-browser +git diff --check +``` + +For browser-sensitive changes, also run: + +```bash +node v3/tools/check-fs.js +``` + +## If The Working Tree Is Dirty + +This repo often has historical or recovery edits. Do not stage everything blindly. + +Use targeted staging: + +```bash +git add README.md index.html vercel.json docs/ +git add LICENSE SECURITY.md CONTRIBUTING.md CODE_OF_CONDUCT.md .github/ +git add v3/assets/screenshots/ +``` + +Then inspect: + +```bash +git diff --cached --stat +``` + +Only commit once the staged set is intentional. diff --git a/index.html b/index.html index 0e15088..6bb133c 100644 --- a/index.html +++ b/index.html @@ -4,41 +4,93 @@ - Tributary Pro v3.10 + FutolStructure | Structural Engineering Workbench + + + + + + + + + + + -
-

🏗️ Tributary Pro v3.10

-

Redirecting to application...

-

If not redirected, click here

-
+
+ FutolStructure logo +

FutolStructure

+

Opening the structural engineering workbench.

+ Open App +
- \ No newline at end of file + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..523093a --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://futolstructure.vercel.app/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..d4ce337 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://futolstructure.vercel.app/ + weekly + 1.0 + + diff --git a/v3/_logs/AGENT_RAG.md b/v3/_logs/AGENT_RAG.md new file mode 100644 index 0000000..4c47c37 --- /dev/null +++ b/v3/_logs/AGENT_RAG.md @@ -0,0 +1,608 @@ +# FutolStructure Agent RAG + +Last updated: 2026-04-26 + +This file is the repo-local retrieval source for Codex/agents working on FutolStructure. +Read this before continuing refactor, debugging, report work, export work, or demo preparation. + +## Retrieval Protocol + +1. Read this file first. +2. Run `git status -sb`. +3. Preserve the current branch and dirty worktree. +4. Do not commit, push, or create a PR unless explicitly instructed. +5. Work narrowly from the current branch state. +6. Use `v3/_logs/PROGRESS.md` only for older historical context. +7. Prefer grounded fixes over broad rewrites. + +## Current Agent Operating Mode + +- Claude Code should no longer be treated as an active dependency or required collaborator. +- `v3/_logs/CLAUDE_CODE_SHARED.md` is historical handoff context only unless the user explicitly reactivates it. +- Codex should continue from the local repo state, this RAG file, and user-provided conversation summaries. +- Lum's guidance can be used as product/roadmap context, but code changes must still be grounded in the current FutolStructure implementation. + +## Current Branch + +- Repo: `michaelfutol/futolstructure` +- Local path: `D:\projects\futolStructure 04-14-26\tributary-pro-v2.0` for the current open workspace. Intended renamed path is `D:\projects\futolStructure 04-14-26\futolstructure` once VS Code/Codex releases the folder lock. +- Branch: `recovery/fs-clean-reapply-offsets` +- Last approved pushed checkpoint: + - Commit: `f545cfa998625323e88f14132d6b33c4466388e8` + - Message: `feat: add FSTR format and TAN report scaffold` + +## Current Product Name + +- Working shorthand: `FS` +- Current product: `FutolStructure` +- Possible future product name: `FutolRCStructures` +- File format: `.fstr` +- Manual calculation/report identity: `TAN` = `Technical Analysis Notes` +- Professional TAN title: `STRUCTURAL DESIGN AND ANALYSIS CALCULATIONS` +- TAN subtitle: `Technical Analysis Notes (TAN)` +- Do not use `Technical Analysis Notebook` in professional-facing report titles. + +## Product Doctrine + +FutolStructure is a geometry-first reinforced concrete structural modeling, tributary/load-intent, and solver-handoff program. + +It is not primarily a holistic permit-pack app. + +Core role: + +- structural geometry modeling +- gravity load path intent +- tributary analysis visualization +- member/load governance +- schedules and preliminary quantities +- STAAD/ETABS/IFC/DXF/Revit-handoff direction +- standard report and TAN/manual calculation scaffold + +Visualization doctrine: + +- FutolStructure should become visually professional before chasing full finite element analysis. +- The existing 3D geometry/topology engine is valuable because it already has member coordinates, slab surfaces, structural hierarchy, and generated topology. +- Near-term visualization work should polish rendering and add engineering-grade overlays without pretending to be true ANSYS-level FEA. +- Pseudo stress/utilization overlays are acceptable when clearly labeled as load/reaction/utilization visualization, not finite element stress results. +- Full finite element meshing/contour/deformation is a later optional layer, not a live-test stabilization requirement. + +Not the core role: + +- raw messy plan cleanup +- raster tracing as analysis truth +- final compliance solver replacement +- all-discipline permit pack generator + +Ecosystem separation: + +- BOOM / Plan Cleaner owns dirty PDF/DWG/DXF cleanup and layer doctrine. +- FutolStructure consumes clean structural geometry and computes/governs structural intent. +- STAAD / ETABS / SAFE / STAAD Foundation remain recognized solver/compliance tools. +- Future holistic permit pack can be another app/version that combines disciplines. + +## Engineering Discipline + +Rules unless user explicitly overrides: + +- Do not commit. +- Do not push. +- Do not create PR. +- Do not merge to main. +- Stop at diff/report unless explicitly instructed. +- Keep edits narrow. +- Avoid broad UI overhaul. +- Avoid fake solver truth. +- Clearly warn when a feature is scaffold/preliminary only. + +## Current Reliable Envelope + +FS is currently most suitable for: + +- regular orthogonal RC residential buildings +- 2 to 3 storeys for validation/demo +- simple rectangular bay grids +- suspended upper floors and roof slab +- no-GF suspended baseline when ground floor slab is ground-bearing +- basic column/beam/slab/footing generation +- tributary gravity-load visualization +- preliminary member schedules +- `.fstr` save/load +- STAAD export as the preferred solver path +- standard report and TAN scaffold + +Use caution for: + +- 5 to 10 storey modeling-only demos +- irregular layouts +- transfer beams +- large cantilevers +- planted columns +- custom/stair beams +- non-regular solver exports + +Do not claim as complete yet: + +- final RC design +- final BBS/cut-bend +- full ACI 318/NSCP detailing +- final seismic/lateral/drift design +- final ETABS solver-result reconciliation and engineering acceptance +- full SAFE/STAAD Foundation handoff +- complete Revit model authoring + +## Active Local Work Since Last Checkpoint + +`v3/index.html` has uncommitted local changes after commit `f545cfa`. + +Known local improvements already made: + +- Plot button visible in toolbar. +- TAN button visible in toolbar. +- `.fstr` support exists from checkpoint. +- Plan/report paper sizes split: + - Plans: `20x30`, `A3`, `A2`, `A1` + - Reports/spreadsheets: `A4`, `Letter`, `Legal/Long` +- Tributary view presentation is being refined toward engineering-sheet style. +- Concrete strength display includes MPa and psi. +- Beam Loads panel table raw-HTML bug fixed. +- Beam Loads panel now displays: + - slab/wall line load + - actual BxH self-weight line load + - total reaction including factored self-weight + +Before committing any future checkpoint, verify these changes are intentional. + +## One-Week Demo Target + +Target period: 2026-04-26 to 2026-05-03 + +Goal: FS Demo-Ready RC Residential Modeler. + +Demo workflow should show: + +1. Create a regular 2 to 3 storey RC building. +2. Edit spans, floors, columns, beams, slabs, and footings. +3. Run tributary gravity analysis. +4. Show Layout, Tributary, Foundation, 3D, schedules. +5. Save/load as `.fstr`. +6. Export a usable STAAD model. +7. Generate standard report and TAN scaffold. +8. Plot current plan with title block. +9. Clearly show limitations/warnings. + +Demo files to prepare later: + +- `Demo_01_2Storey_Regular.fstr` +- `Demo_02_3Storey_Residential.fstr` +- `Demo_03_STAAD_Handoff.fstr` + +Demo outputs to capture: + +- Layout plan screenshot +- Tributary plan screenshot +- Foundation plan screenshot +- 3D screenshot +- Beam/column schedule screenshot +- Standard PDF report +- TAN report +- STAAD export file + +## Lum Roadmap Context - Visualization and FEA Direction + +Latest user-provided Lum guidance: + +- Do not chase ANSYS-level finite element visuals immediately. +- First become reliable, stable, professional, visually clean, and export-correct. +- FutolStructure already has the important foundation for future FEA: + - geometry generation + - member topology + - slab surfaces + - beam/column coordinates + - structural hierarchy + - nodes/connectivity/topology concepts +- Phase 1: professional 3D visualization. + - ambient lighting + - edge highlighting + - soft shadows + - smooth transparency + - proper slab edges + - beam/column face shading + - hover highlights + - cleaner lineweights + - stress-like visual overlays only when clearly labeled + - animated load arrows + - deformation amplification display only from valid result data +- Phase 2: pseudo structural visualization before true FEA. + - beam utilization heatmap + - footing pressure heatmap + - tributary intensity color + - column load gradient + - slab load contour approximation + - clear labels that these are tributary/load/utilization visualizations, not finite element stress results +- Phase 3: real finite element layer, later. + - meshing + - shell elements + - node connectivity + - stiffness matrix solving + - interpolation + - contour extraction + - displacement fields +- Strong later feature: import STAAD reactions/displacements and display deformed shape in FutolStructure. + +Recommended sequence: + +- M0-M5: current stabilization and professionalization. +- M6: STAAD export truth. +- M7: visualization polish. +- M8: pseudo stress/utilization overlays. +- M9: optional FE slab meshing layer. +- M10: hybrid AI-assisted structural review. + +## Milestone Schedule + +### M0 - Current Live Testing Stabilization + +- Fix visible test bugs immediately. +- Keep browser app usable. +- Ensure Beam Loads and Column Loads are readable. +- Ensure no raw HTML appears in side tables. +- Keep Foundation plan visible. +- Keep `.fstr` roundtrip stable. + +### M1 - Member Size Truth + +- Column `B/X` means plan X direction. +- Column `H/Y` means plan Y direction. +- Beam `B x H` means width x depth. +- Enforce actual member sizes in: + - Layout + - Tributary + - Foundation + - 3D + - schedules + - BOQ/BOM + - reports + - TAN + - exports where supported + +### M2 - Tributary Presentation + +- Match Layout gridline style. +- Use restrained engineering palette. +- Light slab area fills. +- No heavy text masks. +- Labels must remain readable at zoom. +- Avoid overlap between `A` and `w` labels. +- Prefer beam/column detailed load values in side panel unless selected. + +### M3 - 3D Professional View + +- Keep stable non-twitchy render loop. +- Use wider ground plane/grid if helpful. +- Keep actual member dimensions visible. +- Add member click/hover info window later. +- Avoid eye-straining colors. + +### M4 - Reports and TAN + +- Standard report: model summary, load summary, schedules, warnings. +- TAN: old-school manual calculation notes style. +- TAN should include: + - assumptions + - formulas + - substitutions + - result lines + - representative beam check + - representative column load takedown + - representative footing check + - simple stress/force illustrations + - warnings and solver handoff notes + +### M5 - Plot and Paper Setup + +- Plan sheets: + - 20 x 30 inch + - A3 + - A2 + - A1 +- Reports/spreadsheets: + - A4 + - Letter + - Legal/Long +- Plot current: + - Layout + - Tributary + - Foundation +- Include title block: + - project name + - sheet title + - scale placeholder + - date + - revision + - prepared by + - checked by + +### M6 - STAAD Handoff + +- STAAD is the preferred current solver path. +- ETABS 22 OAPI model-builder baseline is validated for native E2K round-trip and gravity/modal analysis in ETABS 22.6. +- SAFE handoff uses CSI's supported ETABS `Story as SAFE V12 .f2k` export; direct browser-authored F2K remains out of scope until validated against SAFE. +- Do not silently export unsupported non-regular beams. +- Report export exclusions honestly. + +### M7 - Load Governance + +- Keep old global wall load for backward compatibility. +- Add explicit wall load objects. +- Add explicit point load objects. +- Wall loads should eventually include: + - type: external/internal/parapet/partition + - support mode + - thickness + - height + - unit weight + - finish allowance + - opening deduction + - lintel allowance + - geometry reference +- Point loads should eventually include: + - water tank + - truss reaction + - roof frame reaction + - equipment + - target: joint/beam/column/slab-warning-only + +### M8 - RC Design and Detailing + +Future goal: + +- ACI 318 / NSCP aware beam design +- column axial/slenderness/interaction checks +- slab one-way/two-way design +- footing bearing, punching, one-way shear, flexure +- development length +- hooks and bends +- laps +- BBS +- cut and bend schedule +- structural shop drawing/detailing support +- practical construction detailing, not only code equations +- future Revit detailing handoff after FS produces trustworthy bar intent + +Do not claim complete until implemented and validated. + +## Naming Conventions + +Preferred future conventions: + +- Cantilever beam: `CB-{floor}-{gridColumn}` + - Example: `CB-2F-D3` + - Means cantilever beam on floor `2F` connected at column/grid `D3`. +- Edge beam: `EB-{floor}-{gridStart}{gridEnd}` + - Example: `EB-2F-D3D4` + - Means edge beam on floor `2F` spanning adjacent grid points `D3` to `D4`. + +Existing legacy/internal IDs may still exist and must be migrated carefully: + +- `BX-*` +- `BY-*` +- `BCX-*` +- `BCY-*` + +Do not rename broadly without compatibility checks. + +## Known Technical Gaps + +### Loads + +- Beam self-weight is calculated from actual BxH and added to column loads. +- Beam Loads panel now displays self-weight, but analysis path must avoid double-counting. +- Slab self-weight is based on slab thickness x concrete density. +- Explicit wall load objects are not yet fully mapped into analysis/export. +- Point loads are not yet fully mapped into analysis/export. + +### Non-Regular Beams + +Current classes: + +- regular +- custom/added +- stair-ready +- cantilever +- cantilever_edge + +Known truth gap: + +- schedules/report can see more beams than exports can safely handle. +- custom column-to-column beams are the first plausible exportable subset. +- beam-to-beam custom beams need node insertion and host beam splitting. +- free-end beams need support doctrine. +- cantilever and cantilever_edge need endpoint/load/export contracts. + +### ETABS + +- The former hand-written E2K exporter was invalid and failed ETABS 22.6 import; it is superseded by the ETABS 22 OAPI builder. +- The OAPI builder creates a real `.edb`, asks ETABS to export its own native `.e2k`, and writes a model-count audit JSON. +- Olango baseline validation passed ETABS 22.6 native E2K round-trip and analysis with zero negative stiffness eigenvalues. +- Current validated scope is geometry, concrete/rebar materials, frame/slab sections, fixed base supports, gravity load patterns, area loads, wall line loads, and ULS combinations. +- Wind, seismic, diaphragms, detailed mass-source governance, solver-result reconciliation, and final member design remain engineer-review items. +- SAFE remains a supported handoff through ETABS `Story as SAFE V12 .f2k`; do not claim a direct SAFE writer until SAFE import is independently validated. + +### Revit / IFC + +- IFC2x3 Coordination View geometry handoff is implemented from the same corrected active-model payload used by ETABS and STAAD. +- Current IFC scope includes every active column, regular/cantilever/custom beam, and exact active slab polygon, organized by building storey with concrete material and `Pset_FutolStructure` source metadata. +- IFC schema validation and geometry creation passed independently in IfcOpenShell 0.8.5 for the corrected Olango model: 2 storeys, 32 columns, 80 beams, 42 slabs, and 154/154 products with valid geometry. +- Autodesk Revit `Open IFC` / `Link IFC` remains an explicit validation gate. Do not claim Revit import success until a full Revit executable is installed and the generated file is opened or linked there. +- Revit Structure can model concrete members and rebars, but realistic ACI/NSCP rebar detailing is difficult and time-consuming when done manually. +- FS should eventually become the RC detailing intent engine before trying to automate Revit rebar: + - beam main bars + - stirrups + - column vertical bars + - ties/confinement zones + - footing mats and dowels + - slab bars + - hooks, laps, development lengths + - BBS and cut/bend schedule +- Revit should later receive structured geometry/detailing data through IFC, schedules, Dynamo, or a Revit add-in/API bridge. +- Do not claim Revit-ready rebar automation until FS has validated detailing rules and export path. + +### Permit Pack + +FS can support structural/civil permit outputs. +It should not become the all-discipline permit pack app. +Future holistic permit pack should be separate or later integrated at ecosystem level. + +## User Testing Mode + +The user is testing real structures and will report bugs. +The active test set is three real residential projects needed for building-permit submission within the next week. + +Current testing priority: + +- Keep the latest local build open in browser when requested. +- Treat user-reported issues as highest priority. +- Fix small blockers quickly. +- Keep changes narrow and report clearly. +- Prefer structural/civil reliability over cosmetic polish. +- Do not overclaim permit/submission readiness. +- FS remains the structural program; broader all-discipline permit pack remains future ecosystem work. + +## Active Live-Test Issues + +- 2026-05-12 FS-038F scope: remaining cantilever problem is now treated as slab ownership and tributary source-of-truth correction, not export/report work. No commit, push, PR, feature expansion, STAAD/ETABS export edits, TAN edits, or report edits for this pass. Tributary regions must be generated from final active slab polygons/bounds only. Cantilever slabs must be independent closed regions, not parent-bay overlays. No tributary polygon should extend outside its owning slab polygon. Beam self-weight must remain separate from tributary slab area; tributary area represents slab load contribution, while beam DL/member self-weight is added separately to reactions. +- 2026-05-12 FS-038F implementation note: cantilever slab final render/analysis bounds now drive slab load area, tributary polygons, area-balance display, labels, and hit-testing. Cantilever slab ownership is conservative single-source for now: the final clipped cantilever slab polygon is owned once by its resolved main support beam. CB/EB members frame the cantilever edge but no longer receive hidden duplicate slab tributary area without visible slice polygons. Synthetic validation for Axis A/B3/Axis 4-style strips produced 100.0% area balance, zero slice-outside-owner violations, and no hidden cantilever beam area. +- 2026-05-12 FS-038G cantilever drafting doctrine: CB/cantilever beams inherit the resolved supporting main beam BxH. EB/edge beams default to 150 mm width, with depth tied to the adjacent main/support beam; narrower 130 mm is only a future engineer-reviewed minimum, not the default. If an EB is explicitly enlarged above 150 mm or carries direct wall/line load, treat it as a load-bearing/designed edge-beam candidate requiring explicit load intent review, not automatic tributary slab-area inflation. Cantilever slab hatch/tributary bounds must terminate at resolved physical faces, not raw centerlines. Where perpendicular cantilever strips meet, generate only the missing corner infill slab and split its tributary ownership between the two adjacent support beams so corners are filled without overlapping double area. +- 2026-05-12 FS-038H local L-corner patch: user screenshot showed remaining local/offset cantilever L-corner issues and `Area Balance = 80.0%`. Root causes were: global corner infill only handled building exterior corners, not local perpendicular cantilever patch overlaps; and `getSlabLoadArea()` still counted void/deleted slabs in area balance even though load distribution skipped them. Patch adds local perpendicular cantilever overlap resolution: parent strip rectangles are trimmed out of the shared corner and one `SC-C--` local corner slab owns that area. Void/deleted slabs now return zero load area. Validation: synthetic local L-corner overlap was converted from two overlapping rectangles into horizontal strip + vertical strip + corner infill with unchanged union area and zero overlap; void-slab test returned 100.0% area balance. +- 2026-05-12 FS-038I CB size inheritance: CB/cantilever side beams must inherit the same-axis main beam they extend, not the perpendicular perimeter support beam. Example right-edge `CB-2F-E3`/`BCX-R-3` extends `B-2F-D3E3`/`BX-3-4`; its `supportingBeamId` may still be the slab perimeter support `BY-5-3`, but its `supportingMainBeamId` must be `BX-3-4` for sizing/drafting. Patch adds same-axis main-beam resolution for top/bottom/left/right CB start/end roles, prioritizes `supportingMainBeamId` in CB size resolution, and re-normalizes CB sizes after regular beam sizing so display, schedule, self-weight, and 3D use the inherited main beam BxH. Validation forced `BX-3-4 = 350x650` and `BY-5-3 = 200x450`; `BCX-R-3` resolved to `350x650`, proving it follows the main beam, not the smaller perimeter support. +- 2026-05-12 FS-038J cantilever hatch/beam-body overlap: cantilever slab hatch/analysis bounds must stop at rendered physical beam faces everywhere, not at raw support centerlines. This matters when the cantilever support resolves inward to a beam such as `B-2F-D3E3` instead of the global exterior line; `columnAlignment = outer` is not enough to infer zero support trim. Patch adds `getCantileverSupportFaceCoord()` using the actual support beam rendered centerline offset and width, then uses that face coordinate for slab bounds and CB/EB support trims. Validation: inward bottom cantilever `SC-B4` supported by `BX-3-4` started exactly at the support beam face with support-beam overlap `0`; a broader sweep across support/side/tip cantilever beams checked 12 related slab-beam pairs and found overlap count `0`. +- 2026-05-12 FS-039 foundation tie-beam face alignment: Foundation plan tie beams should connect face-to-face between actual column stubs. Do not center perimeter tie beams blindly on the raw grid edge or use gridline-only alignment; this can make the tie beam miss the practical column-face band. Patch adds foundation column-rect helpers and draws tie beams from actual column face to column face, with the transverse tie-beam band centered inside the common column-face overlap. Validation intercepted foundation canvas rectangles for a 300 mm column / 200 mm tie beam edge case: top tie beam now runs from `x=0.30` to `x=3.70` and `y=0.05..0.25`, not old grid-edge `y=0.00..0.20`. +- Issue #1: Cantilever local patch display must keep the `CB-*` projection visibly equal to the entered cantilever length, with a dimension label where practical. If `EB` is checked, FS now creates the outside tip edge beam plus the closing/parallel edge beam for the local patch. EB plan rendering must honor `planStartTrim`/`planEndTrim` in both analysis-style and Layout/framing render paths so the EB starts/ends at the faces of CB/adjacent beams instead of visually passing through centerlines. EB tags must stay short (`EB-RF-E1-T`, `EB-RF-E1-C` style); offsets/runs such as `1.20m` belong in dimension text, not embedded in member names. EB default is a practical preliminary 150 mm width, with depth tied to the adjacent main beam; this is not a code-certification rule and still needs engineer/code review before submission detailing. +- Cantilever EB sizing must stay consistent through generation, schedule, and 3D. Do not allow stale `120 x 120` generated/override values to survive sizing. Current doctrine: cantilever edge beams use practical preliminary `150 mm` width and depth from the adjacent main beam, with `400 mm` fallback if no adjacent beam depth is resolvable. +- Cantilever slab void/delete behavior: cantilever slabs should be toggleable to VOID like regular slabs. Because small cantilever/landing panels overlap CB/EB hit zones, click handling should prioritize cantilever slab body hits before structural beam hits. Cantilever tributary labels should show actual `w`, not `w 0.0`, unless the slab is voided. +- Issue #2: One-storey/single-slab modeling must be possible. Removing `RF` should work when the user wants a single remaining floor/slab. +- Issue #3: Real framing plans need beam and column alignment adjustment by axis/gridline, not only one-off member nudges. Current per-column/per-beam offsets help, but axis-level batch alignment remains a needed workflow improvement. +- Issue #4: Cantilever slabs must not always force an outside edge beam. Short flat extensions or landing stubs around 1.0 m to 1.2 m may be slab-only with drip mold; the cantilever/support beam remains, but the outside `EB` should be optional per cantilever span. +- Issue #5: Rebar/BBS schedule tabs need an obvious return path to the active plan view. Browser refresh must restore the working drawing through local autosave instead of falling back to the default 2x2 starter model. +- Foundation/Layout/Tributary canvas tabs should regenerate from the current model state when opened, not only redraw whatever geometry happened to be in `state.beams`/`state.slabs` from the previous tab. +- Foundation linework should remain legible at zoomed-out permit-plan scales; avoid subpixel dashed footings/tie beams that make the plan look blank. +- Issue #6: Cantilever modeling contract: the cantilever value is always the outward projection depth. Along-edge run defaults to the full bay/span, matching the older regular cantilever behavior. Special local/landing patches use the separate `Run` input; `Run = 0` means full span, while `Run = 1.20` with projection `1.20` creates the `1.20 m x 1.20 m` local patch. `Off` only applies when `Run > 0`. The `EB` checkbox only controls whether outside edge/corbel beams are created; unchecked means slab-only outside edge. Visible labels should use `CB-*` for cantilever beams and `EB-*` for cantilever edge beams; legacy internal IDs may remain until migration is safe. +- Issue #7: CB/EB joint representation must stay centerline-true while visually trimming to member faces in plan views. Current pass creates one local CB at the cantilever start line and, if enabled, tip/closing EBs for the local projection. EB preliminary section is 150 mm wide with depth tied to the adjacent main beam where resolvable. +- CB visual length must not cancel normal column-face trimming. The rendered CB should run from support face to cantilever tip, while the stored `span` remains the entered cantilever projection for load/report purposes. +- Issue #8: Cantilever rows now have an `Off` field in meters. This moves the local projection start along the supporting edge span, allowing landing-like patches away from the grid start. Axis-level batch beam/column alignment is still not fully solved; current state has per-member column nudge and beam offset controls plus column flush. +- Hit testing for columns/beams should use the same rendered/flush/nudge geometry as the drawing path. Do not use raw `col.x`/`beam.x1` bounds for member selection after alignment controls are active. +- Bulk alignment control is intentionally type-specific: column context menu can nudge/reset all columns; beam context menu can offset/reset all generated regular beams on the active floor. Do not move beams and columns together from one command, and do not batch-offset cantilever/edge/custom beams until their joint topology is safer. +- Nudge/offset input must use the user-facing plan convention: `+X` moves right, `-X` moves left, `+Y` moves up, `-Y` moves down. Internally the canvas Y axis grows downward, so column Y inputs and horizontal/X-beam transverse offsets must be inverted before applying to model coordinates. +- Column adjustment is now individual by default, with Ctrl-click multi-select for case-by-case groups such as selected exterior columns. Selected columns get an amber outline and the column context menu moves/resets the selected group when opened from one of those selected columns. +- Layout plan labels use full schedule IDs for normal/CB beams, but cantilever edge beams use shorter plan labels without embedding the floor or projection length. Full EB IDs remain available in schedules/reports. Projection dimensions belong as dimension text, not inside member names. +- Cantilever edge beam sizing should not allow stale `120 x 120` values to override the current preliminary rule. EB width defaults to 150 mm, optional 130 mm should require engineer review, and EB depth follows the adjacent main beam where resolvable. +- Closing cantilever edge beams may be described as a cantilever corbel edge subtype for governance/report clarity, but they still remain under the `cantilever_edge`/`EB-*` member class until a solver-safe CCB class is explicitly designed. +- Cantilever tributary slice labels should use the local support run of the cantilever patch, not the full adjacent bay span, so a `1.20 m x 1.20 m` patch displays a realistic local line load instead of a diluted bay-wide value. +- Right-click hit testing must prioritize cantilever slabs before CB/EB members so the slab delete/restore context menu remains reachable even when the panel is small and surrounded by cantilever beams. +- In `Col: Flush`, cantilever closing/corbel edge beams start from the already-flushed outside support face. Do not add another main-beam half-width trim in that mode; otherwise the EB appears to gap away from the support beam. +- When an outside EB is enabled, the cantilever beam visual segment should stop at the inside face of the tip EB to reduce solid-model overlap in Layout/Tributary/3D views. Keep solver centerline metadata separate from this visual/member-face trimming. +- Current CB/EB joint stabilization uses explicit generated render endpoints (`renderX1/renderY1/renderX2/renderY2`) for cantilever beams and cantilever edge beams. Stored centerline geometry, IDs, spans, and support metadata remain unchanged, but Layout/Tributary/3D hit geometry can render CB/EB members face-to-face. Example bottom-edge contact rule: CB runs from support-beam outside face to tip-EB inside face, tip EB starts at CB side face, and closing EB starts at support-beam outside face and ends at tip-EB inside face. +- Future feature request: copy beam layout from the current/source floor to lower, upper, selected, or all typical floors. Options should eventually include custom beams, deleted regular beams, beam offsets, beam sizes, cantilevers, and possibly voids/openings, with warnings if target grids or column availability differ. +- Future feature request: add a dedicated BBS/Rebar Layout Plan view beside the existing BBS table. The plan should show assigned bar marks/bar numbers on the member layout, tied back to the BBS rows and later to ACI/NSCP development length, hooks, laps, bends, and cut-length logic. Do not add this during live-test stabilization unless explicitly prioritized over cantilever/foundation consistency. +- Future feature request: Manual Calculations/TAN tab should eventually show accurate beam analysis figures and data from the model, including representative beam geometry, clear span, tributary slab area, slab/wall/self-weight line loads, total factored line load, support reactions, and diagrams. This is not part of FS-038F; handle later under reports/TAN/manual calculation milestone. +- Future roadmap topic: Automated beam design/calculation sheets for OBO-friendly documentation are feasible and strategically important, but must be built in layers. Separate analysis from design: + - Analysis data: geometry, clear span, support conditions, load combinations, shears, moments, reactions, deflections, and solver-imported results where available. + - Design data: required bars, selected bars, stirrups/ties, spacing, cover, development length, hooks, laps, cut lengths, checks, schedules, and BBS linkage. + - Preferred staged implementation: (M1) manual calculation notebook scaffold, (M2) single representative beam calculation sheet, (M3) single beam detail drawing/elevation/sections, (M4) beam schedule sync, (M5) BBS linkage, then columns, footings, and slab strips. + - Strategic doctrine: do not try to replace STAAD/ETABS first; become the transparent engineering documentation layer above solver results. OBO value comes from readable assumptions, load path, traceable calculations, organized schedules, and professional beam details/shop-drawing-like sheets. +- Deleted beam truth: `floor.deletedBeams` must affect the generated analysis model, not only drawing/3D visibility. Deleted beams are filtered from `state.beams` before tributary distribution, reactions, and column load accumulation. Cantilever slab/CB/EB generation must not attach to a deleted beam. If the original perimeter support beam is deleted, the cantilever support should resolve inward to the next available parallel beam on that floor. "Available" means the beam ID is not deleted and its two endpoint columns are active on that floor; only if no support beam remains should the cantilever generation be skipped. Restoring the perimeter beam may move the saved cantilever intent back to the original edge support because the cantilever inputs remain in the floor data. +- Cantilever slab delete semantics: deleting a cantilever slab is not the same as marking a regular slab void. It clears that cantilever intent (`cantilevers`, `Run`, `Off`) for the active floor/span, so the cantilever slab plus related CB/EB members disappear from Layout/Tributary/3D/load paths. Regular internal slab deletion still uses `voidSlabs` because those represent openings/void panels. Deleted beams should not be drawn as red ghost members in Layout/Tributary; they should disappear from generated display and calculations, with Undo or future restore controls used for recovery. +- Cantilever boundary occupancy: before creating any CB/EB, resolve whether an active beam already occupies the candidate boundary segment. Active means not deleted and support columns still active when applicable. Reuse existing occupied side/tip/free-edge beams instead of generating duplicate cantilever members. EB should connect between the two effective side supports, whether those supports are generated CBs or existing beams. This prevents overlapping plan lines/labels and keeps cantilever load/support metadata tied to actual framing geometry. Stale `SC-*` cantilever slab IDs must be purged from `floor.voidSlabs`; cantilever slabs are removed by clearing cantilever intent, while regular slab voids/openings remain in `voidSlabs`. +- CB/EB visual face truth: tip edge beams must start/end at the actual face of the effective side support, not the raw centerline. Face resolution must consider generated CB width, reused active side-beam width, beam alignment offsets, and the active column face when the side support lands on a grid column. Structural-plan columns should mask beam linework below their hatch so support joints read as clean column-beam intersections instead of overlapping dashed lines. +- Full-bay cantilever side members are CB continuations on both gridline sides. For example, a left-edge cantilever from gridline 1 to gridline 2 should create/reuse `CB-*A1` and `CB-*A2` as horizontal continuations of the main beams on those axes; only the outside free-edge member is an `EB`. Off-grid/local closing sides may still be represented as EB/corbel edges until a richer local framing class exists. +- Cantilever projection input is the outside slab limit. When EB is enabled, the EB section should sit inside that projection with its outside face at the slab edge, not with its centerline on the slab edge. CB visual segments should stop at the inside face of the EB while stored spans keep the entered projection depth. +- When EB is disabled for a cantilever, the side CB continuation(s) still exist where applicable and should render all the way to the free slab edge. The three exposed slab-only perimeter sides must be shown as clear solid slab edges while the supported side remains governed by the main support beam. +- When EB is enabled for a cantilever, the Layout/framing plan must still show the cantilever slab body under the EB/CB linework. EB is a boundary/member inside the slab projection, not a void that leaves an empty panel. +- 3D cantilever display may differ slightly from 2D drafting trim: CB members should render as full concrete members up to the cantilever slab edge in 3D, while 2D plans may trim CB/EB joints for readable drafting. Cantilever slab boxes should use their full projected panel without visual inset and with stronger opacity/edge outlines so they read as real slabs. +- Future delete architecture: preferred long-term behavior is hard delete from the live model with recovery through undo/redo snapshots. Until that refactor is safe, every renderer/calculation/export path should read active-only filtered geometry. Red deleted ghosts should not appear in normal engineering mode; if needed later, deleted/void debug overlays should live behind a debug mode. +- 2026-05-01 stabilization update: normal Layout/Tributary rendering no longer draws red void/deleted slab ghost panels. `SC-*` cantilever slab IDs are treated as cantilever intent, not regular void slabs; attempting to void a cantilever slab clears the cantilever intent instead of adding it to `voidSlabs`. +- 2026-05-01 3D update: 3D slab boxes now sit above beam tops with stronger opacity and separate cantilever slab color so slab solids read more clearly. 3D CB members trust explicit generated render endpoints; EB-on/EB-off behavior should come from generation instead of being re-extended blindly in the 3D renderer. +- 2026-05-01 corner cantilever update: when two exterior cantilever strips meet at a building corner, FS generates a small corner cantilever slab infill (`SC-C-TL`, `SC-C-TR`, `SC-C-BL`, `SC-C-BR`) only when both adjacent support beams remain on the exterior support lines. Corner load is conservatively shared to the two adjacent resolved support beams; do not treat this as final RC corner-cantilever design. +- 2026-05-02 Codex audit/stabilization update: removed the nested context-menu `toggleBeamDeleted` shadow so beam delete uses the global per-floor deletion path; removed duplicate `window.updateSlabParam` overwrite and kept the undo/clamp/floor-sync implementation; schedule bulk beam/slab deletes now route through `floor.deletedBeams`/`floor.voidSlabs` instead of transient `.deleted` flags; cantilever slab load sharing to side CBs now distributes across active generated side beams; 3D/export floor geometry collectors restore columns and aggregate sizing state after temporary regeneration; derived corner cantilever slabs no longer swallow left-click deletion when no adjacent intent can be cleared; 3D non-cantilever slabs now get a full-extents outline so visual inset does not read as missing slab. Checks passed: `node --check` for engine files, inline-script parse with Node `vm.Script`, `git diff --check` with line-ending warnings only, and Playwright screenshot load of `v3/index.html`. +- FS-040 foundation tie-beam offset extension: Foundation plan tie beams were temporarily wired to inherit the matching generated beam transverse offset (`BX-*` / `BY-*`) through `state.beamAlignmentOverrides`. This proved unsafe because Foundation and floor beam offsets became coupled. Superseded by FS-041. +- FS-041 foundation/beam offset decoupling correction: FS-040 coupling was unsafe because Foundation tie-beam offsets reused `beamAlignmentOverrides`, so Foundation edits moved 2F/RF beams and floor beam resets moved Foundation tie beams. Replaced this with independent `foundationTieBeamAlignmentOverrides` keyed by the foundation tie-beam geometry. Right-clicking a Foundation tie beam now opens a tie-beam offset action that writes only to the foundation store. Layout/Roof regular beam offsets no longer move Foundation tie beams. Cantilever side beams (CB) now ignore their own beam offset overrides and inherit the same-axis supporting regular main beam offset, so CB drafting follows the main beam instead of drifting independently. CB/EB offset controls are hidden; offset the supporting regular beam instead. Browser CDP isolated-profile probe verified: foundation tie offset shifts only Foundation, floor `BX-*` offset does not shift Foundation, Foundation right-click selects `foundationTieBeam`, and `BCY-T-1` inherits `BY-1-1` offset while ignoring its own stale override. +- FS-042 recovery note after workspace contamination/reset: local uncommitted `v3/index.html` changes were lost by a `reset: moving to HEAD` event on 2026-05-14, and no GitHub/stash copy contained the missing offset/cantilever fixes. Recovery restored the safety-critical pieces from RAG doctrine: Save/Undo/Redo controls, persisted `columnPositionOverrides`, per-floor `beamAlignmentOverrides`, independent `foundationTieBeamAlignmentOverrides`, column/beam context-menu nudge actions, foundation right-click tie-beam offset routing, hit testing based on rendered/nudged beam/column geometry, deleted regular beams filtered from generated analysis/display geometry, cantilever support fallback to the next available inward support beam when the exterior support is deleted, and CB sizing/offset inheritance from the same-axis supporting main beam. Keep SolverLink or other lean-modeler experiments outside this workspace unless work is intentionally branched/committed first. +- FS-046 clean reapply path: from clean checkpoint `f545cfa`, cherry-picked the pre-broken offset/cantilever recovery commit `2110b0a` and intentionally did not apply broken WIP `9f5cd6c`. Reapplied deleted-geometry hiding without the bad global `col.active === false` veto: void slabs are not drawn in normal Layout/Tributary, inactive/deleted columns are hidden instead of red/dashed ghosts, active slab area/status/material counts filter `!isVoid`, and display regeneration now happens before `updateResults()` so current-floor voids update `Total Slab Area`. Browser probe: default `80.0 m²` became `60.0 m²` after voiding `S1`; member count changed `9C / 12B / 4S` to `8C / 10B / 3S`; red canvas ghost pixel count stayed `0`; column/beam/foundation offset context-menu functions were present. +- FS-047 undo/rebar stabilization: toolbar Undo/Redo is the active recovery path. Beam add/delete, slab void, and planted-column placement must call `saveStateSnapshot()` before mutation; do not add new actions to the old `state.undoStack` path. Preliminary column rebar suggestions should use a balanced tied-column bar count: round required bars up to at least 4 and then to the next even number, so 5 required bars displays as 6 bars instead of an unbalanced 5-bar layout. +- FS-048 FSTR/refresh recovery: legacy FSTR files may store cantilevers only at root/project level. On load, migrate that root cantilever set into floors that do not have their own `floor.cantilevers`; then sync the visible cantilever inputs from the active floor. Save/load now includes `columnAlignment`. The browser also writes a local autosave snapshot after calculation and on pagehide, and restores it silently on startup so Refresh does not return to the starter model. Rebar and BBS tabs are hidden from the main plan toolbar for now; schedule/design panels get a `Back to Plan` escape button. +- FS-049 recovery/debugging mode: added a `Recovery` tab under Output as the first stop before more cantilever geometry work. It exposes active floor, grid size, local autosave metadata, per-floor `deletedBeams`, `voidSlabs`, `deletedColumns`, and cantilever length mismatches. Recovery actions: refresh inspector, sync left cantilever inputs from active floor, apply left cantilever inputs to active floor, restore active-floor deleted/void geometry, restore all floors deleted/void geometry, clear local autosave, and download a state snapshot. Model-changing recovery actions must call `saveStateSnapshot()` first so Undo can reverse accidental recovery. +- FS-050 explicit FSTR load safety: opening a project file now quarantines loaded `deletedBeams`, `voidSlabs`, and `deletedColumns` instead of applying them silently. This prevents old/broken hidden geometry from making slabs or columns disappear immediately on file open. The Recovery panel shows quarantined counts/raw data and has an explicit restore action if the hidden state was actually intentional. Direct file open also clears local autosave before applying the file so stale browser state cannot fight the loaded project. Cantilever inputs now sync from the active floor on floor switch/render so a 4x3 opened project shows 4 top/bottom and 3 left/right controls instead of default 2x2 controls. +- FS-051 stricter recovery defaults: browser autosave restore also quarantines hidden geometry so stale broken local state cannot keep re-hiding slabs on startup/refresh. Hidden-state quarantine now clears stale beam/slab locks together with deleted beams, void slabs, and deleted columns; restore-current/all-floor recovery actions also clear locks. Lock/unlock now records an undo snapshot. +- FS-052 product identity cleanup: repo-local branding strings were changed from Tributary Pro / tributary-pro naming to FutolStructure / futolstructure across active source, docs, logs, and legacy helper scripts. Remote already points to `michaelfutol/futolstructure`. Physical folder rename from `tributary-pro-v2.0` to `futolstructure` was attempted but Windows denied it because the current workspace folder is locked by an open process; perform after closing VS Code/Codex workspace or reopening from a renamed folder. +- FS-053 left-panel/void safety: undo/redo now refreshes span inputs, floor tabs, and cantilever inputs after restoring state so a 4x3 model cannot keep showing stale 2x2 cantilever controls. Plain left-click on slabs no longer voids/deletes slab panels; intentional slab deletion must use the right-click member menu, with undo snapshot support still handled by the void toggle path. +- FS-054 repeatable recovery smoke checks: added `v3/tools/check-fs.js`, a dependency-free Node check that parses inline scripts, runs engine syntax checks, opens the local app through Chrome/Edge CDP, verifies default 2x2 and 4x3 model column/slab counts, confirms cantilever input counts follow the active grid through undo/redo, legacy `.fstr` root-cantilever loads, and browser reload autosave restore, confirms column/beam/slab delete undo recovery, confirms void slabs are not drawn as red ghosts in normal canvas view, confirms plain slab left-click does not create voids, and confirms loaded hidden geometry (`deletedBeams`, `voidSlabs`, `deletedColumns`, stale locks) is quarantined instead of applied silently. Use `node v3/tools/check-fs.js` before/after geometry, load/save, undo/redo, or rendering fixes; use `--no-browser` for syntax-only checks. Undo/redo snapshot creation/restoration was centralized with `createStateSnapshot()` and `restoreStateSnapshot()` to reduce recovery drift. Column toggles, 3D beam/custom-beam deletion, and 3D box-selection deletion now enter the same snapshot path so accidental deletion can be recovered from the toolbar undo. Stale `lockedBeams`/`lockedSlabs` now count as quarantine payload even when no deleted/void geometry is present. +- FS-055 cantilever dashboard self-healing: `calculate()` now normalizes active/global and per-floor cantilever arrays against the current `xSpans/ySpans`, then repairs the left cantilever dashboard if the DOM input counts do not match the active grid. This prevents stale 2x2 controls from surviving after a 4x3 model is loaded/restored or after state changes bypass a direct `renderCantileverInputs()` call. `updateSpan()` also invokes the dashboard guard. Smoke coverage now intentionally corrupts the cantilever panel to stale controls and verifies `calculate()` rebuilds it to 4 top / 3 right / 4 bottom / 3 left. +- FS-056 cantilever local-patch restoration: cantilever span values are now normalized to object specs `{ projection, run, offset, eb }` while still accepting legacy numeric FSTR values. The left dashboard exposes Projection, Run, Off, and EB per span; `Run = 0` means full bay/span and positive `Run` creates a local patch such as `projection: 1.2, run: 1.2` for a stair support landing. Cantilever slab generation uses resolved run bounds, so local patches no longer force full-length slabs. CB/EB generation uses the same bounds and the EB checkbox suppresses only the free-end edge beam, not the slab itself. EB width defaults to 150 mm unless explicitly overridden, while EB depth inherits from its supporting main beam where resolvable. Cantilever edge beams render inward: the EB outside face is aligned to the free slab edge, so the beam sits inside the slab projection instead of straddling outside the slab. Layout now marks active slabs with small `1W`/`2W` symbols and cantilever slabs with `C`, making missing/deleted slabs visible even without Tributary hatch. Smoke coverage verifies legacy root cantilever migration into the new object form, the 4x3 dashboard counts, a right-edge `1.2 m x 1.2 m` local patch, EB on/off behavior, inherited EB depth, and free-edge EB face alignment. +- FS-057 drafting labels: Layout beam tags now append the resolved beam section size in parentheses (`(250x400)`, `(150x550)`, etc.) directly to the plan label, including CB/EB generated members. Layout slab notation follows the user-approved arrow guide: one-way slabs show a single directional slab arrow with one-sided/half arrowhead and a small tail tick in the primary short-span/load-transfer direction; two-way slabs show crossed single directional slab arrows in both directions; cantilever slab arrows follow the cantilever projection direction. Do not use normal two-sided/V arrowheads for slab symbols. The slab mark (`S1`, `S2`, `SC-*`, etc.) sits near the arrow symbol, and slab thickness is shown separately in a small slotted/oblong capsule below the symbol (`150`, etc.) with no `t=` prefix. Tags use current floor slab thickness unless an individual slab thickness is present. Keep this as drafting annotation, but its one-way orientation must stay aligned with the tributary calculation rule (`lx <= ly` means horizontal/X short-span load transfer; otherwise vertical/Y). +- FS-058 Olango real-project regression: `v3/tools/check-fs.js` now accepts `--project ` and loads a real `.fstr` through the browser `applyLoadedProject()` path. Current target: `D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr`. Latest pass confirms no Init error, 4x3 grid, cantilever controls restore to top 4 / right 3 / bottom 4 / left 3, saved inactive columns A3/A4/B4/E4 remain hidden as intended (16 visible from 20 generated), all 12 regular slabs remain active, and normal structural-plan canvas has zero red ghost pixels. The test now derives the expected quarantine count from the actual saved file payload; earlier copies had saved void slabs S5/S9/S10/S12, but the current on-disk Olango file has empty hidden/void arrays. Verification: `node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr"`. Screenshot: `C:\Users\Futol\AppData\Local\Temp\futolstructure-project-futolstructure_2026-05-22---olango.png`. +- FS-059 browser-session discipline: avoid opening fresh user-visible browser windows for every check. Manual app launches should open a tab in the existing Edge session and include a cache-busting query after UI edits, e.g. `Start-Process "msedge.exe" -ArgumentList "http://127.0.0.1:4173/v3/index.html?fsBust="`. Browser smoke tests support `--keep-browser` or `FS_KEEP_BROWSER=1`; this leaves the CDP test browser alive on `FS_CDP_PORT` so later smoke runs add tabs to the same controlled instance instead of spawning/killing a new one. `check-fs.js` no longer passes `--new-window`, disables Network cache where CDP allows it, and opens `index.html` with a smoke cache-busting query. +- FS-060 cantilever EB depth inheritance: `getBeamSizeMm()` now prioritizes inherited support depth for `cantilever_edge` / EB members before generated EB fallback dimensions. Explicit beam-size overrides still win, but stale generated `400`/old EB dimensions should no longer block the EB from inheriting the supporting beam depth. Smoke check verifies a right local `1.2 m x 1.2 m` patch creates an inward 150 mm EB whose outside face aligns with the slab free edge and whose depth inherits `BY-5-1 = 550 mm`. +- FS-061 intentional deleted/void slab persistence: the earlier recovery quarantine was too broad and caused legitimate saved slab deletions to reappear after opening a file. New saves now include `hiddenGeometryPolicy: preserve-intentional-hidden-geometry`. `applyLoadedProject()` still quarantines legacy/untrusted hidden geometry when requested, but files/autosaves with this policy preserve intentional `voidSlabs`, `deletedBeams`, `deletedColumns`, and locks as model state. Smoke coverage now checks both paths: a legacy hidden payload is quarantined, while a policy-marked saved `voidSlabs: ['S1']` remains active after load and reduces active regular slab count to 11. Existing older saved files without the policy must be opened, edited/deleted as intended, and saved once with the updated build before intentional deletions persist across future loads. +- FS-062 CB/EB plan label and slab annotation pass: cantilever side beams now carry `nearestSupportColumnId`, and Layout plan labels route CB/EB members through `getBeamScheduleId()` instead of raw internal IDs. Visible CB convention is `CB--` such as `CB-2F-E1`; internal `BCX/BCY` IDs may remain implementation details. CB sizing still follows the same-axis main beam it extends. EB members now explicitly store `supportingMainBeamId = supportingBeamId` for the adjacent parallel support beam, and `getBeamSizeMm()` uses that main/support beam depth for EB depth while keeping the 150 mm preliminary EB width unless overridden. Layout slab annotation keeps the approved one-sided arrow symbols, but chooses a horizontal row for wide/shallow panels and a vertical stack for deeper panels so the slab mark and oblong thickness tag are less likely to shrink or collide. Smoke coverage verifies the right local patch EB label/depth source and `CB-2F-E1` naming/250x400 inheritance. Verification: `node v3\tools\check-fs.js --keep-browser --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr"`. +- FS-063 CB dimension drafting rule: standard CB labels should not append `(BxH)` in the structural plan because a CB is assumed to match the connected same-axis main beam. Keep the clean `CB--` label for non-tapered CBs. If a future tapered CB is introduced, the plan label may show a compact tapered note with at least the end/tip depth, but do not add ordinary section dimensions to normal CB tags. Regular beams and EB members may still show section dimensions for now. +- FS-064 autosave hidden-geometry guard: browser autosave restore now force-quarantines hidden/deleted geometry even when the autosave payload contains `hiddenGeometryPolicy`. This prevents old browser state from silently reopening with stale `voidSlabs`, deleted beams, deleted columns, or locks and making slabs look persistently missing in Tributary/Layout. Explicit `.fstr` project load can still preserve intentional hidden geometry when saved with the policy, but autosave startup is treated as unsafe and pushes those items into the Recovery inspector instead. Smoke coverage writes a policy-marked autosave containing `S1` and `BX-1-1`; startup restore now returns 12 active regular slabs and zero active hidden arrays while recording the payload in quarantine. +- FS-065 CB alignment and cantilever slab edge visibility: CB/cantilever side beams now inherit the full resolved transverse plan offset from their same-axis supporting main beam, not only the manual nudge value. This keeps CB drafting aligned with main beams through column flush/edge alignment and manual beam offsets while retaining inherited main-beam width/depth. Layout and Tributary views now draw cantilever slab perimeters as solid visible lines instead of dashed/weak borders so the free slab edge is readable even when hatch/labels are congested. Smoke coverage sets `BX-1-4` to a nonzero transverse offset and verifies `BCX-R-1` reports the same rendered offset and inherited `250x400` size. +- FS-066 slab annotation uniformity: Layout/Tributary slab annotation text now uses the same black Arial plan-text style/scale as beam labels instead of larger bold blue/brown styling. Slab symbols and thickness capsule text are black for regular and cantilever slabs. Cantilever slabs keep internal `SC-*` IDs for generation/load logic, but plan display maps active cantilever slabs to `CS-1`, `CS-2`, etc. to avoid confusion with future `TS`/other slab classifications. The older brown cantilever `C:` fill label was replaced by the same black `CS-*` mark. +- FS-067 active-only slab display numbering: visible slab marks in Layout/Tributary are now display marks, not raw generated IDs. Regular active slabs are renumbered `S1`, `S2`, ... by top-to-bottom rows and left-to-right within each row, skipping `isVoid`/deleted slabs so missing panels do not leave numbering holes. Active cantilever slabs are likewise sorted top-left and displayed as `CS-1`, `CS-2`, ... independent of internal `SC-*` IDs. Smoke helper now has a synthetic mark-ordering check where void `S1/S5` cause displayed active regular slabs `S2/S3/S4/S6` to become `S1/S2/S3/S4`. +- FS-068 cantilever L-corner ownership and CB support-side alignment: perpendicular cantilever slab rectangles now resolve L-corner overlaps by trimming the top/bottom strip where it overlaps an active left/right strip, so the shared corner is owned once and the area is not double-counted. This fixed real `0.25 m2` overlaps detected in Olango at bottom/left and bottom/right L-corners. Cantilever side-beam support lookup now prefers the same-axis main beam on the cantilever side of an interior support line, with inward fallback only when the outside-side beam does not exist. Example: right-side `CB-2F-D3` now inherits from `BX-3-4` (`D3-E3`) instead of `BX-3-3` (`C3-D3`), and RF inherits the `BX-3-4` offset. Off-grid local CB ends fallback to the resolved support beam for size inheritance. Standard cantilever slab arrow symbols were removed from Layout annotation; cantilever slabs keep black `CS-*` plus oblong thickness only. Smoke coverage now asserts, for the Olango `.fstr` on both `2F` and `RF`, zero cantilever side-beam size/alignment mismatches and zero active cantilever slab rectangle overlaps. +- FS-069 trusted autosave deleted-slab persistence: startup autosave restore no longer force-restores deleted slabs for current-build trusted autosaves. Autosaves written by this build include `autosaveMeta.stateRevision = FS-069-trusted-hidden-geometry-autosave`; those preserve intentional `voidSlabs`/deleted geometry just like an explicit `.fstr` file. Older/untrusted autosaves still quarantine hidden geometry to avoid stale broken-browser state. `generateSlabs()` now treats a saved parent slab ID as void for any generated sub-slab pieces, and orphan cleanup keeps parent IDs valid when sub-slabs exist. Olango disk payload was verified to contain `2F voidSlabs = S5,S9,S10,S12`; project smoke loads it with `activeRegularSlabs = 8`, `voidRegular = S5,S9,S10,S12`, and `21S` instead of `25S`. Smoke coverage now checks both paths: legacy autosave hidden geometry is quarantined, trusted autosave keeps `S1` void and active regular slabs remain 11. +- FS-070 active slab truth collector: persistent/reappearing slab diagnosis is now governed by one active-slab source. Added `getActiveSlabs()`, `getActiveRegularSlabs()`, `getActiveCantileverSlabs()`, `isActiveSlab()`, and browser audit `collectActiveSlabTruthAudit()`. Layout rendering, Tributary fill/slices, status member counts, area balance, hit testing, 3D floor geometry, and slab load summary now use the same active-only slab collector instead of separate `state.slabs.filter(!isVoid)` paths. Cantilever slab tributary area is owned once by the resolved main support beam; CB/EB members keep self-weight/member behavior but no longer receive hidden duplicate slab tributary area. Olango `.fstr` verification: `16C / 41B / 21S`, active regular slabs `S1,S2,S3,S4,S6,S7,S8,S11`, inactive/void regular slabs `S5,S9,S10,S12`, active cantilever slabs `13`, `Total Slab Area = 138.3 m2`, `Area Balance = 100.0%`, area delta `0`, zero red ghost pixels, and Layout/Tributary tab switching preserves identical active/rendered/accounted slab IDs. +- FS-071 deleted-before-save slab persistence guard: `buildProjectData()` now normalizes current-floor transient hidden slab flags (`slab.isVoid`, legacy `slab.deleted`, or `active === false`) into persisted `floor.voidSlabs` before writing `.fstr`/autosave. The Slab Schedule now reads from `getActiveSlabs()` instead of raw `state.slabs`, so a deleted/void slab cannot remain visible in schedules after disappearing from Layout/Tributary. Legacy schedule bulk slab delete, if surfaced later, now writes `floor.voidSlabs` and recalculates instead of setting transient `slab.deleted = true`. Smoke coverage simulates a slab already hidden by `slab.deleted` before save; the saved project carries it as `voidSlabs: ['S2']` and reloads with 11 active regular slabs. +- FS-072 recovery warning status: legacy `.fstr` files without `hiddenGeometryPolicy` still quarantine hidden/deleted geometry by design, but the app must not show a green complete state if that quarantine leaves active slab area and beam tributary area unbalanced. `updateStatusBar()` now keeps a persistent `Recovery Review Required`/`Area Balance Warning` state, the Recovery panel repeats the warning detail, and the area-balance row marks mismatches with a review hint. Smoke coverage accepts old untrusted Olango only when the warning is visible; current trusted Olango must still be balanced. Verification: `node v3\tools\check-fs.js`, `node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr" --keep-browser`, and `node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-04-26 - Olango.fstr" --keep-browser`. +- FS-073 Olango D3/E3 cantilever L-corner cleanup: user markup required no generated CB where an existing beam already occupies the segment, CB alignment to follow the same-axis main beam, no visible/internal duplicate line at same-elevation L-corner slab joins, and no cantilever slab perimeter line overlapping beam linework. Generation now checks active beam occupancy before creating CB/EB members, reuses the existing occupied beam ID, builds side metadata from the final overlap-trimmed cantilever slab bounds, and skips side CBs that would only represent an internal L-corner trim cut. Layout/Tributary cantilever slab perimeters now draw only exposed perimeter segments after subtracting the support edge, shared cantilever slab edges, and beam-coincident intervals. Olango verification: `BCX-R-3`/`CB-2F-D3` is no longer generated because `BX-3-4`/`B-2F-D3E3` owns that line; `SC-B4` keeps only `BCY-B-5`; `SC-R3` reuses `BX-3-4` plus generated `BCX-R-4`; member count is `16C / 40B / 21S`; active slab area balance remains `100.0%`; new smoke diagnostics assert zero side-CB-over-regular-beam overlaps on both 2F and RF. Verification: `node v3\tools\check-fs.js --no-browser` and `node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr" --keep-browser`. +- FS-074 main-beam/CB rendered-axis alignment: CB side beams must not merely inherit the main beam BxH and transverse offset; they must render on the exact same transverse axis as the same-axis main beam they extend. The structural-plan drift came from cantilever side beams with one column endpoint and one free endpoint averaging a nudged/flush column position with a raw free endpoint. `getRenderedBeamPlanSegment()` now locks a CB side beam's transverse rendered coordinate to its `supportingMainBeamId` segment before applying the inherited offset. Smoke diagnostics now include `renderedAxisMismatch`, not only size/offset mismatch. Olango verification: `BY-5-2`/`B-2F-E2E3` and `BCY-B-5`/`CB-2F-E3` both render on Y-beam axis `13.72`; `BX-4-3`/`B-2F-C4D4` and `BCX-R-4`/`CB-2F-D4` both render on X-beam axis `10.585`. Verification: `node v3\tools\check-fs.js --no-browser` and `node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr" --keep-browser`. +- FS-075 plan measure/dimension tool: Layout/Tributary/Structural plan views now have `Measure` and `Clear Dims` toolbar controls. Measure mode uses two canvas clicks to add a floor-scoped dimension annotation with extension lines, tick marks, and a meter label; it is saved under `floor.planDimensions`, preserved by `.fstr`/autosave serialization, and restored by undo after clear. Measure mode disables pan/add-beam conflicts, keeps a crosshair cursor, supports live preview, and Escape cancels the pending point or exits the mode. This is drafting annotation only and does not affect analysis, tributary area, loads, schedules, or solver export. Browser validation used the in-app Browser against `http://127.0.0.1:4173/v3/index.html`; direct `file://` navigation was blocked by Browser policy, so smoke now supports `FS_APP_URL` for local HTTP validation. Verification: `node v3\tools\check-fs.js --no-browser` and `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr" --keep-browser`. Smoke confirms a 3-4-5 m dimension saves as length `5`, `Clear Dims` removes it, undo restores it, and Olango remains `16C / 40B / 21S` with `Area Balance = 100.0%`, zero red ghost pixels, and no cantilever alignment/overlap diagnostics. +- FS-076 column safety lock and Measure accuracy pass: added a global `Cols Free` / `Cols Locked` toolbar toggle. When locked, column toggle/delete, nudge/reset, all-column nudge/reset, planted-column creation, planted-floor edits, click-to-place columns, 3D column double-click delete, 3D box-selection column delete, and schedule bulk column delete are blocked. The flag persists through `.fstr`/autosave via `columnPositionLocked` and is intentionally treated as a safety setting, not analysis geometry. Locked beams/slabs now render as muted gray indicators instead of blue; globally locked columns also render gray in Layout/Tributary/Structural plan views. Measure now has an `Ortho` toggle and automatic entity snapping while measuring. Snap candidates include grid intersections/lines, active column centers/faces, beam axes/faces/endpoints, custom beams, active slab edges/corners, and existing dimension endpoints; a small amber marker shows the acquired snap point. Smoke coverage verifies the column lock blocks a column toggle and saves as locked, entity snap acquires a near column/grid intersection, Ortho constrains to the dominant axis, and Olango remains stable. Verification: `node v3\tools\check-fs.js --no-browser` and `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr" --keep-browser`. +- FS-077 Tributary/Layout beam visual alignment repair: replaced the duplicated plan beam drawing math with shared `getBeamPlanDrawGeometry()`. Beam cross-axis drawing now uses the beam/grid axis plus beam offset instead of inheriting column face offsets, so Layout and Tributary read from the same beam center/face reference. Wireframe/drafted beams now trim only to a covered junction length based on the connected perpendicular beam and column size; this closes the small visible notch when a column is narrower than the beam without changing analysis geometry. Added smoke coverage that shrinks A1 to 200x200 against 250mm beams and verifies top edge face stays flush at the grid and the horizontal/vertical beam junction overlaps rather than gaps. Latest validation used `D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr`: `16C / 40B / 21S`, area balance `100.0%`, `redPixels: 0`, no cantilever alignment/overlap diagnostics, and `junctionGapM = -0.05`. +- FS-078 saved beam-offset interpretation correction: FS-077 initially over-corrected by ignoring column-position adjustments on the beam cross-axis, which made the same saved Olango `.fstr` appear with a different layout after reload even though `beamAlignmentOverrides` and `columnPositionOverrides` were still present. Correct rule: generated beams should ignore only the automatic `Col: Flush`/outer-column half-width display shift on the cross-axis, but must still honor user-saved manual column nudges (`columnPositionOverrides`) plus saved beam offsets (`beamAlignmentOverrides`). The June 2 Olango file contains 8 column-position overrides and 19 beam-alignment overrides; these are model intent, not transient renderer data. Validation after correction: `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9237'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed with `16C / 40B / 21S`, active regular slabs `8`, cantilever slabs `13`, `Area Balance = 100.0%`, `redPixels = 0`, no cantilever alignment/overlap diagnostics, and small-column junction `topEdgeTopFace = 0`, `junctionGapM = -0.05`. +- FS-079 beam lock enforcement and batch lock: `lockedBeams` must be a real edit lock, not only a gray/lock-icon display state. Added active-floor toolbar batch toggle `Beams Free` / `Beams Locked`, which locks/unlocks all active generated/custom floor beam IDs and persists through `floor.lockedBeams`. A locked beam now blocks direct context-menu offset/reset/delete, all-generated-beam offset/reset skips locked beams, schedule beam size edits are disabled and API-guarded, old bulk-delete paths skip locked beams, and 3D double-click/box-selection deletes skip locked beams. Because generated beam geometry follows support columns, column nudge/reset/toggle/planted-floor edits now also block when they would move a connected locked beam. Smoke coverage locks all active beams, then verifies locked `BX-1-1` cannot be offset, resized, deleted, or moved indirectly by toggling support column `A1`. Validation command: `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9243'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr" --keep-browser`; pass result includes `16C / 40B / 21S`, `Area Balance = 100.0%`, `redPixels = 0`, no cantilever diagnostics, and lock warnings for blocked offset/resize/delete/connected-column toggle. +- FS-080 optional external-foundation mode: added persisted `foundationMode` with `plan` and `baseReactionsOnly`. Plan mode keeps the existing footing/tie-beam design path. Base-reaction mode disables footing/tie-beam inputs, relabels Foundation/Footing tabs to `Base Rxn`, skips footing design, clears footing/tie-beam dead loads and quantities, preserves column self-weight in base reactions, makes foundation tie-beam hit testing return no members, draws a Base Reaction Plan with reaction arrows/labels and a reaction table, and exports `base_reactions.csv` for external SAFE/STAAD Foundation workflows. Smoke coverage verifies mode save/load, zero foundation tie-beam segments, zero footing/tie-beam DL, zero footing sizes, nonzero base reactions, and disabled tie-beam controls. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9243'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr" --keep-browser`; plus in-app Browser interaction confirmed `Foundation: Base Rxn`, disabled tie-beam width, and Base Rxn tab labels. Headless Chrome visual capture saved `C:\Users\Futol\AppData\Local\Temp\futolstructure-base-reaction-plan.png`. +- FS-081 beam line column-face termination: plan drawing now uses column-face trimming for generated beam rendered extents in Layout/Tributary and for Measure beam-axis/face snap candidates. The older junction trim remains available for diagnostics, but visible beam linework must stop at the connected column faces and not run under the column hatch. Smoke coverage now shrinks `A1` to `200x200` against `250 mm` beams and asserts the horizontal and vertical beam endpoints land exactly on the start/end column faces (`faceDeltaM = 0`) while the small-column beam-to-beam junction still overlaps by `50 mm` instead of gapping. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9366'; $env:FS_CDP_PROFILE_DIR=; $env:FS_KEEP_BROWSER='1'; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed with `16C / 40B / 21S`, `Area Balance = 100.0%`, `redPixels = 0`, and no cantilever diagnostics. +- FS-082 small-column beam junction mask: FS-081 over-trimmed visible beam linework to column faces, which made CB/beam terminations look like floating caps when a column is shallower than the connected beam. Correct drafting rule: Layout/Structural generated beam linework should extend into the perpendicular beam/column junction using `trimToJunction`, while the column hatch first paints an opaque white mask over the column footprint and then draws the hatch/outline. This hides the line inside the column but keeps the beam visually connected to the perpendicular beam. Measure snap candidates intentionally stay on `trimToColumnFaces` so the tool snaps to clean grid/column faces, not hidden masked overlap. Smoke now sets `A1 = 300x200`, checks generated beam caps fall inside the column-masked zone, and verifies `BCX-L-1`/`CB-2F-A1` extends `25 mm` past the A1 visible face into the hidden junction. Validation: `node v3\tools\check-fs.js --no-browser`; full browser smoke on `D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr` passed with `16C / 40B / 21S`, `Area Balance = 100.0%`, `redPixels = 0`, and Measure snap restored to `(0,0)`. +- FS-083 void-slab intent must not be garbage-collected: `generateSlabs()` must never silently splice `floor.voidSlabs` during ordinary `calculate()` after span/grid changes. Slab geometry is generated, but the saved deletion intent lives in `floor.voidSlabs`; when an ID no longer matches generated slabs, keep it as user intent, mirror it into `floor.orphanedVoidSlabs`, warn once in console, and surface it in Recovery as `orphan void: ...` for explicit cleanup. Smoke coverage now verifies multi-floor save preserves another floor's `voidSlabs`, and a span change from a model with `S4` void keeps `S4` in `floor.voidSlabs`, `orphanedVoidSlabs`, and saved project data while not applying it to the new smaller slab set. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9381'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed with `2F voidSlabs = S5,S9,S10,S12`, active regular slabs `8`, status `16C / 40B / 21S`, `Area Balance = 100.0%`, `redPixels = 0`, and no relevant project console errors. +- FS-084 plan label collision avoidance: beam labels in Layout/Structural plan now use a collision-aware placement registry instead of fixed above/left positions. The renderer reserves column footprints/column IDs and slab annotation boxes, then tries above/below/far/outside-span positions for horizontal beams and left/right/far/outside-span positions for vertical beams. Font reduction is last-resort and bounded; at very zoomed-out overview, labels that still cannot be placed cleanly are skipped until zoom-in rather than drawn overlapping. Smoke diagnostics expose `window.lastPlanLabelDiagnostics` and fail on forced overlaps. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9393'; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed. Visible Edge reload at 1920x1080 placed all 40 beam labels with `forcedOverlaps = 0`, `skippedLabels = 0`, `shrinkCount = 0`, and model status stayed `16C / 40B / 21S`, `Area Balance = 100.0%`. +- FS-085 beam tag clearance and grid bubble offset controls: beam-tag clearance from beam linework is now based on `0.75 * label font height`, with reduced far/outer candidate distances so cantilever tags do not jump deep into the grid-bubble zone. Added toolbar controls `Bubble -` and `Bubble +N` beside Grid; `state.gridBubbleOffsetPx` defaults to `20`, clamps from `-20` to `90`, persists in `.fstr`/autosave, and affects both Layout/Tributary and Structural/CAD grid bubbles. Dimension labels move partway with the bubbles so they remain between bubbles and the building. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_APP_URL='http://127.0.0.1:4173/v3/index.html'; $env:FS_CDP_PORT='9395'; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed. Visible Edge reload showed `gridBubbleOffsetPx = 20`, `Bubble +20`, all 40 labels placed, `forcedOverlaps = 0`, `skippedLabels = 0`, `shrinkCount = 0`; control test changed `+20 -> +30 -> +20` and preserved zero forced overlaps. +- FS-086 structural DXF layer map: AutoCAD DXF export now uses the structural rows from `D:\BOOM\BOOM\BOOM\FT_LayerMap.xlsx` instead of legacy ad hoc layers. The DXF table declares all structural layers (`S-CONC-FOUND`, `S-CONC-SLAB`, `S-CONC-BEAM`, `S-CONC-COL`, `S-CONC-SW`, `S-CONC-STAIR`, `S-REBAR-GEN`, `S-FORM-*`, `S-MAS-CHB`, `S-STEEL-*`, `S-WP-RAFT`, `S-SOIL-*`, `S-GRID`, `S-TEXT`) with spreadsheet color indexes and lineweights; `S-GRID` uses the `CENTER2` linetype. Current entity mapping: columns -> `S-CONC-COL`, generated beams -> `S-CONC-BEAM`, stair/custom beams -> `S-CONC-STAIR`, slabs -> `S-CONC-SLAB`, grid -> `S-GRID`, text/labels -> `S-TEXT`. Smoke coverage inspects generated DXF text and fails if legacy `GRID/COLUMNS/BEAMS/SLABS/TEXT/CUSTOM_BEAMS` layers return. +- FS-087 typical-from-lower floor layout: added a persisted `floor.typicalFromLower` flag and a left-panel checkbox shown for every floor above the first. When checked, the active floor inherits lower-floor layout/drafting intent before calculate/save/load: cantilevers, stair/custom beams, void slab IDs, slab openings, plan dimensions, deleted beams/columns, and locks. Load values remain floor-specific, so RF can follow 2F geometry while keeping roof DL/LL/thickness/wall-load values. Inherited cantilever inputs are read-only; edit the source lower floor or uncheck the box to make the floor independent. Column `activePerFloor` visibility is mirrored where available, but broader axis-level/batch member-copy workflows remain future work. Validation: `node v3\tools\check-fs.js --no-browser`; `$env:FS_CDP_PORT='9411'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js`; `$env:FS_CDP_PORT='9412'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"` passed. +- FS-088 one-floor minimum and rebuild-up workflow: floor deletion now allows a minimum of one floor/slab. RF is directly removable; deleting RF from the default stack leaves a valid single `2F` floor and keeps that floor's corrected layout intent. `addFloor()` now creates a typical-from-lower copy by default: from one floor it adds an inherited `RF` with roof DL/LL/thickness defaults; from `2F + RF` it inserts an inherited `3F` before RF, and RF follows the new lower floor if it is typical. Floor reindexing now migrates floor-scoped column visibility, planted-floor flags, beam size overrides, and beam alignment override keys so layout state is not orphaned when `2F/3F/RF` IDs shift. Validation added smoke coverage for delete-RF -> one floor -> add RF -> add 3F before RF. Commands passed: `node v3\tools\check-fs.js --no-browser`; `$env:FS_CDP_PORT='9421'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js`; `$env:FS_CDP_PORT='9422'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"`. +- FS-089 typical floor beam override cloning: investigation of the June 2 Olango file showed the file still preserved 2F hidden/layout intent (`voidSlabs = S5,S9,S10,S12`, 40 locked beams) while RF had independent empty `voidSlabs`/`lockedBeams`. The remaining rebuild/add-floor bug was that typical-from-lower copied floor-owned layout intent but did not clone floor-scoped `beamAlignmentOverrides` and `beamSizeOverrides` from source floor keys (`2F::beamId`) to target floor keys (`RF::beamId` / `3F::beamId`). Patch adds `copyFloorScopedModelOverrides()` during typical inheritance, replacing stale target-floor override keys with cloned source-floor values so rebuilt RF/3F floors keep manual beam offsets and sizes. Smoke coverage now seeds a manual beam offset/size on 2F, verifies RF inherits it before save and after reload, verifies RF deletion leaves the 2F override intact, then verifies add-RF and add-3F-before-RF clone the same override chain. Validation passed: `node v3\tools\check-fs.js --no-browser`; `$env:FS_CDP_PORT='9431'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js`; `$env:FS_CDP_PORT='9432'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"`. +- FS-090 beam tag proximity and rebuilt RF slab thickness: user accepted near-beam labels even if they overlap drafting linework, because DXF annotation cleanup can be handled later. Beam labels now use only near candidates at `0.75 * text height` from the beam line/face (`above`/`below` for horizontal, `left`/`right` for vertical), do not try `far`/`outer` placements, and allow overlaps instead of skipping or scattering labels away from their members. Project smoke now asserts no skipped beam labels and no far/outer placements, while allowing forced overlaps. The June 2 Olango `.fstr` stores `slabThickness = 100` on both 2F and RF; add-floor rebuild now preserves the lower floor slab thickness for a newly recreated RF while still using roof DL/LL/wall-load defaults. Validation passed: `node v3\tools\check-fs.js --no-browser`; `$env:FS_CDP_PORT='9433'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js`; `$env:FS_CDP_PORT='9434'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"`. +- FS-091 global beam lock and save-target investigation: user reported RF beams/locks reverted after saving. File audit showed `D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr` still has `columnPositionLocked = true`, 40 locked beams on 2F, but 0 locked beams on RF; the most recently modified `.fstr` was instead `D:\Users\mfutol\Documents\FutolStructure_2026-05-22 - Olango.fstr` at 2026-06-07 03:29 local, with no column lock, no beam locks, RF typical, and only one RF beam alignment override. Root cause for locks: toolbar `Beams Locked` was active-floor only, so locking on 2F did not save RF locks. Patch makes the same toolbar action a universal all-floor beam safety lock: it counts active beams across all floors, locks/unlocks active beam IDs on every floor, persists each floor's `lockedBeams`, and updates the button title/text accordingly. Smoke now fails unless both 2F and RF save full lock lists after the batch beam lock action. Validation passed: `node v3\tools\check-fs.js --no-browser`; `$env:FS_CDP_PORT='9435'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js`; `$env:FS_CDP_PORT='9436'; $env:FS_CDP_PROFILE_DIR=; node v3\tools\check-fs.js --project "D:\Users\mfutol\Documents\FutolStructure_2026-06-02 - Olango.fstr"`. +- FS-092 ETABS 22 and SAFE handoff baseline: replaced the invalid hand-written E2K path with a self-contained PowerShell OAPI builder generated from active FutolStructure model truth. The builder creates ETABS stories, materials, unique frame/slab sections, active columns/beams/slabs, fixed base restraints, `FS_DEAD`/`FS_SDL`/`FS_LIVE`/`FS_WALL` patterns, slab and wall loads, and ULS combinations; then saves a real `.edb`, asks ETABS to export a native `.e2k`, and writes count audit JSON. The June 2 Olango payload produced 2 stories, 32 column segments, 93 beams, 46 slabs, 125 ETABS frame objects, and 46 ETABS area objects. ETABS 22.6 native E2K round-trip returned 0 with identical counts, and ETABS analysis returned 0: seven gravity cases completed, 12 modal modes converged, and the analysis log reported zero negative stiffness eigenvalues. Added a `SAFE` handoff action that generates the same validated ETABS model and instructs the user to use CSI's supported `File > Export > Story as SAFE V12 .f2k File` route; ordinary floors use Floor Loads, while transfer floors/foundation mats use Floor Loads and Loads from Above. Direct SAFE F2K generation is intentionally not claimed because SAFE is not installed locally for import validation. +- FS-093 solver orientation, roof ghost slabs, and ETABS/STAAD parity: FS-092's first validated import still exposed three model-truth defects during visual and cross-solver review. FutolStructure plan Y increases downward, while ETABS/STAAD plan axes increase upward; shared solver payloads now apply `Ysolver = -Yplan`, eliminating the vertical mirror. The June 2 Olango file had RF as an independent legacy floor with no `voidSlabs`, so its exported RF retained `S5/S9/S10/S12`; a dated backup was created and RF was corrected to `Typical from 2F`, copying layout intent, locks, column visibility, and floor-scoped beam overrides while preserving roof loads/elevation. Corrected export is 2 stories, 32 column segments, 80 beams, and 42 slabs, with exactly 21 active slabs on both 2F and RF. The old STAAD writer was not equivalent: it dropped cantilever/custom beams, omitted slab plates, ignored offsets, and used broad floor loads. STAAD now consumes the same solver payload as ETABS, exports all active frame objects plus exact active slab plates and plate loads, connects nearby slab/frame joints with short rigid control/dependent arms without changing slab geometry, uses support-centerline analytical beam axes while retaining drawing axes as metadata, and prints a statics check. Validation: full browser/project smoke passed; ETABS 22.6 created 112 frame objects/42 areas and analyzed with return 0 and zero negative stiffness eigenvalues; STAAD.Pro 2024 batch engine read 146 joints/112 frame members/42 plates and completed with `Warning Count: 0, Error Count: 0`, no disjoint/instability messages, and balanced applied loads/reactions. External live load matched exactly at 414.78 kN. STAAD total dead was 2415.25 kN versus ETABS 2371.49 kN (about 1.8%); this is retained as an explicit self-weight reconciliation item because STAAD uses gross element volumes while ETABS applies its own frame overlap/end-length treatment. Typical-floor regression now fails if upper-floor void IDs or active slab counts differ from the lower source floor. +- FS-094 IFC2x3 BIM geometry baseline: replaced the incomplete legacy IFC writer with a valid IFC2x3 Coordination View exporter driven by `collectCSIExportModelData()`, so IFC now shares the corrected solver orientation and active-model truth with ETABS/STAAD. Products use valid faceted BRep solids and include 32 `IfcColumn`, 80 `IfcBeam`, and 42 `IfcSlab` objects across 2F/RF, concrete material association, storey containment, and `Pset_FutolStructure` source/floor/type/section metadata. Project smoke asserts exact IFC parity with the shared solver payload and can write the generated model with `--write-ifc`. Independent IfcOpenShell 0.8.5 validation reported zero schema issues and generated geometry for all 154/154 products. RF plan inspection confirmed exactly 21 active slabs and absence of deleted `S5/S9/S10/S12`. Validation artifacts are under `D:\Users\mfutol\Documents\FutolStructure BIM Validation 2026-07-02`. Revit import/link is not yet claimed: no full `Revit.exe` or Autodesk Revit product registration was discoverable on C:/D:; only Revit helper components were present. In-app Browser invocation failed because `iab` was unavailable, and Computer Use invocation failed because its native pipe was unavailable, so the existing Chrome/CDP smoke harness was used for browser validation. +- FS-095 STAAD top-view orientation adapter: the shared ETABS/IFC payload correctly maps FutolStructure plan `Y-down` to Cartesian solver `Y-up`, but STAAD uses `X/Z` in plan with `Y` vertical and displays `+Z` downward in its top view. Copying shared solver Y directly to STAAD Z therefore mirrored the plan a second time. STAAD export now applies a target-specific adapter so `Zstaad = original FutolStructure plan Y` while retaining the shared analytical geometry. Slab winding was changed with the adapter so every plate keeps an upward `+Y` normal. Project smoke fails unless the STAAD orientation probe has `Zstaad = sourcePlanY` and every exported slab reports an upward plate normal. Corrected Olango validation passed with 146 joints, 112 frame members, 42 plates, all 42 plate normals upward, and unchanged 32-column/80-beam/42-slab parity. STAAD.Pro 2024 batch analysis completed with `Warning Count: 0, Error Count: 0`; dead load `-2415.25 kN` balanced reaction `+2415.25 kN`, and live load `-414.78 kN` balanced reaction `+414.78 kN`. Artifacts: `D:\Users\mfutol\Documents\FutolStructure Solver Validation 2026-07-02\FutolStructure_Olango_OrientationFixed.std`, its `.ANL`, and `FutolStructure_Olango_STAAD_TopView.png`. +- FS-096 STAAD remaining left/right mirror: user confirmed the actual STAAD-exported building was still mirrored after FS-095, proving the depth-axis correction alone was insufficient. STAAD's target adapter now also maps `Xstaad = -Xplan` while retaining `Zstaad = Yplan`; this is isolated to STAAD and does not change the shared ETABS/IFC Cartesian payload. Because the second axis reversal changes plate handedness, slab point order is reversed so plate normals remain upward. Regression asserts both STAAD target signs, a nonzero X/Y orientation probe, and upward normals for every plate. Corrected V2 artifact: `D:\Users\mfutol\Documents\FutolStructure Solver Validation 2026-07-02\FutolStructure_Olango_OrientationFixedV2.std`. Full saved-project smoke passed with 146 joints, 112 frame members, 42 plates, and 42 upward normals. STAAD.Pro 2024 batch analysis completed with `Warning Count: 0, Error Count: 0`; dead load/reaction balanced at `2415.25 kN` and live load/reaction at `414.78 kN`. The earlier `OrientationFixed.std` is superseded for visual orientation testing. +- FS-097 STAAD readable concrete-design output: live STAAD.Pro Computer Use verification opened V2, switched to Top View, ran Analysis and Design, and reached `Analysis Successfully Completed`, ACI 318-14 concrete design, and `0 Error(s), 0 Warning(s), 0 Note(s)`. Detailed `.ANL` inspection then showed every reinforcement value as `0.00` because design output inherited `kN-m` units and rounded practical steel areas in square metres to two decimals. STAAD export now selects ULS load combinations `3 4`, switches to `UNIT MMS NEWTON` before concrete design, and emits `FC`/`FYMAIN` directly in MPa so required reinforcement is reported in readable `mm2`-scale units. Regression asserts the explicit design load list and N-mm unit transition. This improves reporting only; the model remains a gravity baseline and is not OBO-ready until wind, seismic, drift, diaphragm/mass-source, geotechnical/foundation, and signed-engineer checks are complete. +- FS-098 STAAD displacement evidence: permit-baseline output now includes `PRINT JOINT DISPLACEMENTS` in addition to member forces, support reactions, element stresses, statics checks, and readable concrete design. Regression fails if the joint-displacement command is absent. Gravity displacement is baseline evidence only; lateral drift checks remain blocked on approved wind/seismic inputs and diaphragm/mass-source doctrine. +- FS-099 OBO gravity-baseline audit: Computer Use opened and ran `D:\Users\mfutol\Documents\FutolStructure Solver Validation 2026-07-02\FutolStructure_Olango_GravityBaselineV4.std` in STAAD.Pro Advanced 2024 QA&R. The final visible analysis dialog reported `Analysis Successfully Completed`, ACI 318-14 concrete design, and `0 Error(s), 0 Warning(s), 0 Note(s)`. V4 includes 146 joints, 112 frame members, 42 plates, exact 32-column/80-beam/42-slab parity, 42 upward plate normals, gravity statics balance, ULS design combinations 3/4, readable N-mm reinforcement demand, and joint displacements. Parsed gravity design demand: columns 600-1000 mm2 longitudinal steel; beams about 101-591 mm2. Maximum ULS gravity vertical translation was 3.166 mm at node 65 under combination 3. Created `D:\Users\mfutol\Documents\FutolStructure Solver Validation 2026-07-02\OBO_Permit_Readiness_2026-07-02.md`, explicitly marked not ready for submission. Blocking scope remains project-approved wind/seismic parameters, mass source/diaphragm, drift/P-Delta/stability, wall-load reconciliation, lateral-system/column-dimension and ductile detailing checks, slab/foundation design, plan/calculation reconciliation, and final signed/sealed engineer review. +- FS-100 Santa Magdalena structural-design scope correction: user confirmed the project jurisdiction is Santa Magdalena, Sorsogon and narrowed the requested work to structural analysis/design only, excluding administrative permit-document research. The saved FSTR schema contains no project address, occupancy, wind, seismic, or site-class metadata, so the legacy `Olango` filename is not treated as project-location truth. Added `v3/_logs/STRUCTURAL_ANALYSIS_STATUS_2026-07-02.md` as the structural-only working record. The verified gravity baseline remains unchanged. Preliminary isolated-footing sides based on service D+L, stored 150 kPa allowable bearing, and a provisional 10% footing/soil allowance range from 0.85 m to 1.55 m; peak service reaction is 311.01 kN at base node 15. These are not final designs and must be rerun with project-specific lateral forces, moments, uplift, and geotechnical confirmation. Next solver stage is ETABS lateral/mass/diaphragm/P-Delta/drift analysis after exact site and occupancy inputs are confirmed; STAAD remains the independent gravity/geometry cross-check. +- FS-101 governed ETABS mass/modal baseline and corrected STAAD wall load: dated ETABS working copy `StaMagdalena_ETABS_DiaphragmMassBaselineV2_2026-07-02.EDB` assigns rigid diaphragm D1 to all 42 slabs and uses a governed mass source with element self-mass once, `FS_SDL` at 1.0, `FS_WALL` at 1.0, and both `FS_DEAD` and `FS_LIVE` excluded. `FS_SDL` contains only zero-selfweight superimposed area dead load; `FS_WALL` contains 23 zero-selfweight permanent 2F line loads at the raw 6.0 kN/m source value. ETABS analysis returned 0 with zero negative stiffness eigenvalues. Total translational mass is 225.7232 kN-s2/m, equivalent to 2213.59 kN; cumulative UX/UY/RZ participation exceeds 90% by mode 3 and reaches 100% by mode 6. Mode 1 is Y translation, mode 2 is torsion, and mode 3 is X translation. Exported JSON, CSV, and readable Markdown summaries. FutolStructure export now passes raw wall load to ETABS/STAAD rather than the internal 1.2-factored beam design value, and saved-project smoke asserts this. Corrected STAAD V5 completed ACI analysis/design with 0 errors and 0 warnings: dead reaction 2314.37 kN, live reaction 414.79 kN, and combination-3 vertical translation 3.121 mm by linear superposition at node 65. V4's 2415.25 kN dead result is superseded because it used 7.2 kN/m wall load inside the dead case. Current global wall assignments still require beam-by-beam architectural reconciliation, and final lateral design remains blocked on exact site, occupancy/risk category, NSCP edition, site class, and lateral-system inputs. +- FS-102 ETABS default-diaphragm builder guard: native end-to-end execution of the generated OAPI builder showed that ETABS `NewBlank()` already contains diaphragm `D1`, so blindly calling `SetDiaphragm('D1', $false)` returns code 1. The builder now queries the diaphragm name list, verifies an existing D1 with `GetDiaphragm`, reuses it when rigid, and only deletes/recreates it when semi-rigid; if D1 is absent it creates a rigid D1. Smoke coverage now requires the existing-D1 audit path as well as slab assignments. Native rerun then succeeded, producing a new EDB/E2K with 32 columns, 80 beams, 42 slabs, 42 D1 assignments, and a mass source containing only element self-mass once plus `FS_SDL` and `FS_WALL`. This prevents a valid blank ETABS installation from rejecting the generated model before mass-source creation. +- FS-103 automatic ETABS analysis and modal audit export: the FutolStructure ETABS 22 OAPI builder now proceeds beyond model generation. It saves a dated working EDB, runs ETABS analysis, selects the default `Modal` case, retrieves modal participating mass ratios, exports `_modal_participation.csv`, and embeds the actual `Mass Source Definition`, `Mass Summary by Diaphragm`, and modal rows in `_audit.json`; the original source model is never overwritten. The builder numbers modes sequentially because ETABS returns zero-valued step numbers for this modal-result call. Native Olango validation created 32 columns, 80 beams, 42 slabs, and 42 D1 assignments; analysis returned 0, both mass tables were available with two rows each, all 12 modes exported as 1-12, cumulative UX/UY/RZ reached 1.0, and the ETABS log reported zero negative eigenvalues. Artifacts are under `D:\Users\mfutol\Documents\FutolStructure Solver Validation 2026-07-02\AutoAnalysisValidation`. +- FS-104 FutolStructure application identity mark: added a repo-owned square PNG icon at `v3/assets/futolstructure-icon.png`, using a compact structural-frame/FS monogram in the application's graphite, engineering-blue, concrete-white, and safety-orange palette. The same asset is now the browser favicon, the visible header mark, and the GitHub README identity/download target. It is intentionally text-free and high-contrast so it remains recognizable when scaled down for tabs and future desktop/mobile launchers. + +When a bug is reported: + +1. Reproduce/inspect locally when possible. +2. Fix smallest relevant code path. +3. Run syntax check. +4. Run `git diff --check`. +5. Open browser if requested. +6. Report changed files. +7. Do not commit/push unless explicitly instructed. + +## Browser Launch + +The local app can be opened directly: + +`D:\projects\futolStructure 04-14-26\tributary-pro-v2.0\v3\index.html` + +PowerShell: + +`Start-Process -FilePath (Resolve-Path .\v3\index.html)` diff --git a/v3/_logs/ETABS_SAFE_DEMO.md b/v3/_logs/ETABS_SAFE_DEMO.md new file mode 100644 index 0000000..1745afe --- /dev/null +++ b/v3/_logs/ETABS_SAFE_DEMO.md @@ -0,0 +1,36 @@ +# ETABS and SAFE Demo Runbook + +## Validated Baseline + +- Source model: `FutolStructure_2026-06-02 - Olango.fstr` +- ETABS version: 22.6.0 +- Exported model: 2 stories, 112 frame objects, 42 area objects +- Native E2K round-trip: passed with identical object counts +- ETABS analysis: completed with zero negative stiffness eigenvalues +- STAAD.Pro 2024 batch analysis: completed with zero warnings and zero errors +- Cross-solver external live load: 414.78 kN in both ETABS and STAAD +- Self-weight reconciliation: STAAD dead total 2415.25 kN versus ETABS 2371.49 kN (about 1.8%); review gross-member versus ETABS overlap/end-length treatment before permit issue. +- Gravity cases: `FS_DEAD`, `FS_SDL`, `FS_LIVE`, and `FS_WALL` +- ULS combinations are included; wind, seismic, diaphragm, and final RC design review are not yet automated. + +## ETABS Demo + +1. Open the Olango `.fstr` and click `Run Analysis`. +2. Click `ETABS` and run the downloaded PowerShell builder. +3. In ETABS, show the unmirrored plan, matching 2F/RF slab openings, story definitions, concrete frame/slab sections, fixed supports, load patterns, slab loads, wall line loads, and ULS combinations. +4. Run ETABS analysis and show the analysis log or deformed shape. +5. Explain that the builder saves both a real `.edb` and an ETABS-native `.e2k`, plus an audit JSON with exported object counts. + +## SAFE Demo + +1. Click `SAFE` and run the downloaded handoff builder. +2. In ETABS, use `File > Export > Story as SAFE V12 .f2k File`. +3. Use `Floor Loads` for an ordinary gravity floor. +4. Use `Floor Loads and Loads from Above` for a transfer floor or foundation mat. +5. Open the `.f2k` in SAFE and review units, supports, loads, meshing, and design preferences before analysis. + +## Accurate Claims + +- Say: FutolStructure creates an ETABS 22 gravity-model baseline through CSI OAPI, with native E2K round-trip and ETABS analysis validation. +- Say: SAFE handoff follows CSI's supported ETABS-to-SAFE V12 route. +- Do not claim: completed seismic design, final code-compliant RC member design, or independently validated direct SAFE F2K generation. diff --git a/v3/_logs/PROGRESS.md b/v3/_logs/PROGRESS.md index b6a894d..7379c5b 100644 --- a/v3/_logs/PROGRESS.md +++ b/v3/_logs/PROGRESS.md @@ -1,9 +1,27 @@ -# Tributary Pro v3.0 - Progress Log +# FutolStructure v3.0 - Progress Log ## Purpose This log tracks ALL changes, tasks, and decisions made to the project. **Rule:** Every code change must be logged here with timestamp and description. +## July 2, 2026 + +### 20:50 - Automatic ETABS Analysis and Modal Reporting + +**Summary:** Extended the generated ETABS 22 OAPI builder from model creation into a complete gravity/modal validation handoff. + +**Implementation:** +1. Runs ETABS analysis after saving a dated working EDB. +2. Preserves the governed mass source: element self-mass once, `FS_SDL`, and `FS_WALL`; live-load mass remains excluded pending occupancy confirmation. +3. Exports modal participation ratios to CSV with modes numbered 1-12. +4. Adds the actual mass-source table, diaphragm mass summary, analysis return, and modal rows to the audit JSON. +5. Handles ETABS' existing default `D1` diaphragm without a duplicate-definition failure. +6. Updates Model Readiness text to distinguish validated diaphragm/mass/modal baselines from pending wind/seismic design. + +**Native Validation:** ETABS 22.6 returned 0 for the saved Olango model, exported 32 columns / 80 beams / 42 slabs, reported two diaphragm mass rows, reached cumulative UX/UY/RZ = 1.0, and logged zero negative eigenvalues. + +**Files Modified:** `v3/index.html`, `v3/tools/check-fs.js`, `v3/_logs/AGENT_RAG.md`, `v3/_logs/PROGRESS.md` + --- ## January 28, 2026 @@ -194,3 +212,11 @@ This log tracks ALL changes, tasks, and decisions made to the project. - **Current Working File:** `index.html` (551KB, v3.8 with tabbed schedules) - **Backups Location:** `_archive/backups_20260121.zip` - **This Log:** `_logs/PROGRESS.md` + +--- + +## 2026-07-03 - FutolStructure Application Icon + +- Added `v3/assets/futolstructure-icon.png` as the canonical product icon. +- Wired the icon into the v3 browser favicon and application header. +- Added the logo and a direct asset link to the GitHub README. diff --git a/v3/_logs/STAMAGDALENA_ETABS_MASS_MODAL_SUMMARY_2026-07-02.md b/v3/_logs/STAMAGDALENA_ETABS_MASS_MODAL_SUMMARY_2026-07-02.md new file mode 100644 index 0000000..7821c47 --- /dev/null +++ b/v3/_logs/STAMAGDALENA_ETABS_MASS_MODAL_SUMMARY_2026-07-02.md @@ -0,0 +1,56 @@ +# Sta. Magdalena ETABS Mass Source and Modal Summary + +Run date: 2026-07-02 + +## File control + +- Original retained unchanged: `FutolStructure_ETABS_2026-07-01_221810.EDB` +- Dated working copy analyzed: `StaMagdalena_ETABS_DiaphragmMassBaselineV2_2026-07-02.EDB` +- ETABS analysis return: 0 +- Negative stiffness eigenvalues reported: 0 +- Corrected FutolStructure OAPI builder validation: native EDB/E2K generation succeeded with 32 columns, 80 beams, 42 slabs, and 42 rigid-diaphragm assignments. +- FutolStructure now performs this ETABS analysis and exports mass-source/diaphragm audit JSON plus modal participation CSV automatically from the generated builder. + +## Mass source confirmation + +| Source | Mass multiplier | Confirmation | +|---|---:|---| +| Element self-mass | 1.0 | Included once | +| FS_DEAD | 0.0 | Excluded to prevent duplicate member self-mass | +| FS_SDL | 1.0 | Superimposed slab dead load only; load-pattern self-weight = 0 | +| FS_WALL | 1.0 | Permanent wall/partition line load only; load-pattern self-weight = 0 | +| FS_LIVE | 0.0 | Excluded pending occupancy confirmation | + +Assignment audit: 42 rigid-diaphragm slab areas, 42 SDL-loaded slab areas, and 23 wall-loaded 2F beams at 6.0 kN/m. The legacy global wall load still requires beam-by-beam reconciliation against the architectural wall plan before final design. + +## Diaphragm mass summary + +| Story | Diaphragm | Mass X (kN-s2/m) | Mass Y (kN-s2/m) | Mass moment of inertia | Center X (m) | Center Y (m) | +|---|---|---:|---:|---:|---:|---:| +| RF | D1 | 80.9175 | 80.9175 | 2215.3364 | 7.4180 | -4.4813 | +| 2F | D1 | 144.8057 | 144.8057 | 4150.9601 | 7.4453 | -4.5155 | +| Total | | 225.7232 | 225.7232 | | | | + +Equivalent total seismic weight from the reported translational mass is 2213.59 kN using g = 9.80665 m/s2. + +## Modal participation ratios + +| Mode | Period (s) | UX | UY | RZ | Sum UX | Sum UY | Sum RZ | +|---:|---:|---:|---:|---:|---:|---:|---:| +| 1 | 0.502894 | 0.000183 | 0.918062 | 0.025999 | 0.000183 | 0.918062 | 0.025999 | +| 2 | 0.387033 | 0.043107 | 0.025095 | 0.867077 | 0.043290 | 0.943157 | 0.893076 | +| 3 | 0.350882 | 0.881661 | 0.000438 | 0.043622 | 0.924951 | 0.943595 | 0.936697 | +| 4 | 0.197729 | 0.000023 | 0.054339 | 0.001414 | 0.924974 | 0.997934 | 0.938111 | +| 5 | 0.149099 | 0.002062 | 0.002046 | 0.060386 | 0.927035 | 0.999980 | 0.998497 | +| 6 | 0.132210 | 0.072965 | 0.000020 | 0.001503 | 1.000000 | 1.000000 | 1.000000 | + +Modes 7-12 have approximately 0.0034 s periods and negligible additional UX, UY, or RZ participation. + +## Interpretation and limitations + +- Mode 1 is predominantly Y translation. +- Mode 2 is predominantly torsion. +- Mode 3 is predominantly X translation. +- More than 90% cumulative UX, UY, and RZ participation is reached by mode 3; effectively 100% is reached by mode 6. +- This is a diaphragm and mass-source baseline, not a completed wind/seismic design. +- Final lateral analysis still requires the confirmed occupancy/risk category, exact site, governing NSCP edition, site class/geotechnical basis, lateral-force-resisting system, accidental eccentricity, response-spectrum/base-shear scaling, P-Delta, drift, torsional-irregularity, overturning, and uplift checks. diff --git a/v3/_logs/STRUCTURAL_ANALYSIS_STATUS_2026-07-02.md b/v3/_logs/STRUCTURAL_ANALYSIS_STATUS_2026-07-02.md new file mode 100644 index 0000000..d7563db --- /dev/null +++ b/v3/_logs/STRUCTURAL_ANALYSIS_STATUS_2026-07-02.md @@ -0,0 +1,105 @@ +# Sta. Magdalena Structural Analysis and Design Status + +Status: GRAVITY BASELINE VERIFIED; LATERAL AND FINAL DESIGN PENDING + +This record covers structural analysis, design, detailing, and solver reconciliation only. It does not cover administrative building-permit requirements. + +## Project basis + +- Jurisdiction/location known to date: Municipality of Santa Magdalena, Sorsogon +- Exact barangay, site address, and coordinates: pending +- Occupancy/use and risk category: pending +- Source model: `FutolStructure_2026-06-02 - Olango.fstr` (legacy filename; project metadata is not stored in this FSTR schema) +- Current STAAD baseline: `StaMagdalena_STAAD_GravityBaselineV5_2026-07-02.std` +- Current ETABS modal baseline: `StaMagdalena_ETABS_DiaphragmMassBaselineV2_2026-07-02.EDB` +- Materials: concrete fc' = 21 MPa; reinforcing steel fy = 415 MPa +- Stored allowable soil bearing pressure: 150 kPa, pending geotechnical confirmation + +## Verified solver baseline + +- STAAD.Pro Advanced 2024 completed analysis and ACI 318-14 concrete design with 0 errors and 0 warnings. +- Model inventory: 2 storeys, 146 joints, 32 column segments, 80 beams, and 42 slab plates. +- Active slabs: 21 on 2F and 21 on RF. +- Deleted slab IDs S5, S9, S10, and S12 remain excluded on both levels. +- All 42 slab plates have upward normals. +- Gravity load cases: dead/superimposed dead and live. +- Current strength combinations: 1.2D + 1.6L and 1.4D. +- Dead load/reaction equilibrium: 2314.37 kN. +- Live load/reaction equilibrium: 414.79 kN. +- Maximum primary dead-load vertical translation: 2.014 mm at node 65. +- Maximum primary live-load vertical translation: 0.440 mm at node 65. +- Linear superposition for combination 3 gives 3.121 mm vertical translation at node 65. +- Gravity-only longitudinal reinforcement demand: columns 600-1000 mm2; beams approximately 101-591 mm2. + +The earlier V4 dead-load result of 2415.25 kN is superseded. V4 applied the already factored 7.2 kN/m wall load inside the dead load case. V5 correctly applies the unfactored 6.0 kN/m permanent wall load and leaves the 1.2 factor to the strength combination. + +## ETABS mass source and modal baseline + +- The dated V2 working copy was created without overwriting the original ETABS file. +- All 42 slab areas are assigned to rigid diaphragm `D1`. +- Element self-mass is included once through the element-mass option. +- `FS_SDL` is included at factor 1.0 and contains only superimposed area dead load. +- `FS_WALL` is included at factor 1.0 and contains only permanent wall line load. +- `FS_DEAD` is excluded from the mass source to avoid duplicating element self-mass. +- `FS_LIVE` is excluded pending confirmation that the final occupancy requires a live-load mass fraction. +- Total translational mass is 225.7232 kN-s2/m, equivalent to 2213.59 kN weight using standard gravity. +- Modal cumulative participation exceeds 90% in UX, UY, and RZ by mode 3 and reaches 100% by mode 6. +- Mode 1 is Y-translation dominant, mode 2 is torsion dominant, and mode 3 is X-translation dominant. Accidental torsion and torsional-irregularity checks remain mandatory in the final lateral model. + +## Saved loading assumptions + +- 2F: 3.2 m storey height, 100 mm slab, 2.0 kPa superimposed dead load, 2.0 kPa live load, and legacy 6.0 kN/m wall load. +- RF: 3.2 m storey height, 100 mm slab, 1.5 kPa superimposed dead load, 1.0 kPa live load, and no wall load. +- Global wall load assignment must be reconciled beam by beam before final design. +- No project-specific wind or seismic load is assigned yet. + +## Preliminary gravity footing sizing + +Square footing sides below use service D + L reactions, the stored 150 kPa allowable bearing pressure, a provisional 10% allowance for footing/soil weight, and 50 mm size increments. They are not final footing designs. + +| Base node | D (kN) | L (kN) | Service (kN) | Preliminary square side (m) | +|---:|---:|---:|---:|---:| +| 1 | 77.56 | 12.77 | 90.33 | 0.85 | +| 3 | 142.21 | 24.44 | 166.65 | 1.15 | +| 5 | 187.34 | 35.92 | 223.26 | 1.30 | +| 7 | 156.69 | 28.76 | 185.45 | 1.20 | +| 9 | 103.22 | 17.14 | 120.36 | 0.95 | +| 11 | 77.27 | 12.11 | 89.38 | 0.85 | +| 13 | 185.68 | 32.12 | 217.80 | 1.30 | +| 15 | 249.78 | 50.76 | 300.54 | 1.50 | +| 17 | 216.74 | 41.65 | 258.39 | 1.40 | +| 19 | 116.40 | 17.47 | 133.87 | 1.00 | +| 21 | 119.80 | 20.99 | 140.79 | 1.05 | +| 23 | 213.45 | 39.58 | 253.03 | 1.40 | +| 25 | 188.14 | 33.63 | 221.77 | 1.30 | +| 27 | 78.72 | 12.33 | 91.05 | 0.85 | +| 29 | 101.38 | 17.73 | 119.11 | 0.95 | +| 31 | 99.99 | 17.39 | 117.38 | 0.95 | + +Final footing design must include lateral moments, eccentricity, uplift, sliding, overturning, soil pressure distribution, punching shear, one-way shear, flexure, dowels, tie beams, settlement, and property-line constraints. + +## Analysis and design sequence + +1. Confirm the exact site and occupancy so the wind exposure, hazard basis, and risk category are defensible. +2. Confirm the lateral-force-resisting system and diaphragm assumption in both principal directions. +3. Reconcile all wall, stair, tank, equipment, and other imposed loads with the drawings. +4. Build ETABS lateral cases in both directions, accidental torsion, mass source, modal case, and code combinations. +5. Run P-Delta, drift, torsional-irregularity, stability, overturning, and uplift checks. +6. Reconcile ETABS and STAAD gravity reactions and frame demands. +7. Finalize beam and column reinforcement, shear reinforcement, joints, confinement, laps, and development lengths. +8. Complete slab flexure, shear, deflection, opening, cantilever, and edge-beam checks. +9. Complete isolated-footing and tie-beam analysis/design using final support forces. +10. Reconcile calculations with structural plans, schedules, notes, and details for engineer review and seal. + +## Inputs required for the next solver run + +- Exact barangay/address or site coordinates +- Building occupancy/use and risk category +- Governing NSCP edition +- Site class/geotechnical basis and groundwater condition +- Confirmed allowable bearing pressure +- Lateral system in X and Y +- Diaphragm assumption +- Wind exposure/topographic/enclosure conditions +- Actual wall locations and wall construction +- Concrete cover, preferred bar sizes, and detailing standards diff --git a/v3/assets/screenshots/futolstructure-3d.png b/v3/assets/screenshots/futolstructure-3d.png new file mode 100644 index 0000000..89d122a Binary files /dev/null and b/v3/assets/screenshots/futolstructure-3d.png differ diff --git a/v3/assets/screenshots/futolstructure-plan.png b/v3/assets/screenshots/futolstructure-plan.png new file mode 100644 index 0000000..75aeb16 Binary files /dev/null and b/v3/assets/screenshots/futolstructure-plan.png differ diff --git a/v3/assets/screenshots/futolstructure-stair-builder.png b/v3/assets/screenshots/futolstructure-stair-builder.png new file mode 100644 index 0000000..15af678 Binary files /dev/null and b/v3/assets/screenshots/futolstructure-stair-builder.png differ diff --git a/v3/engine/loads.js b/v3/engine/loads.js new file mode 100644 index 0000000..a7ff85f --- /dev/null +++ b/v3/engine/loads.js @@ -0,0 +1,526 @@ +/** + * engine/loads.js — Structural Load Calculation Module + * Extracted from FutolStructure v3 + * + * All functions accept explicit parameters instead of reading global state. + * This module has zero DOM dependencies and can be tested standalone. + * + * NSCP 2015 (National Structural Code of the Philippines) / ACI 318-14 + */ +'use strict'; + +const EngineLoads = (function () { + + // ======================== + // Floor / Column Helpers + // ======================== + + /** + * Check if floorId is at or above startFloor in the floors array. + * Higher index = higher floor (GF=0, 2F=1, RF=2). + * @param {Array} floors - The floors array + * @param {string} floorId - Floor to check + * @param {string} startFloor - Reference floor + * @returns {boolean} + */ + function isFloorAtOrAbove(floors, floorId, startFloor) { + const floorIdx = floors.findIndex(f => f.id === floorId); + const startIdx = floors.findIndex(f => f.id === startFloor); + return floorIdx >= startIdx; + } + + /** + * Check if a column is active on a specific floor. + * Handles planted columns, per-floor toggles, and legacy active flag. + * @param {object} col - Column object + * @param {string} floorId - Floor to check + * @param {Array} floors - The floors array (for isFloorAtOrAbove) + * @returns {boolean} + */ + function isColumnActiveOnFloor(col, floorId, floors) { + if (!col) return false; + + // Planted columns are inactive on floors BELOW their startFloor + if (col.startFloor || col.isPlanted) { + const startFloorId = col.startFloor; + if (startFloorId && !isFloorAtOrAbove(floors, floorId, startFloorId)) { + return false; + } + } + + if (col.activePerFloor) { + return col.activePerFloor[floorId] !== false; + } + return col.active !== false; + } + + // ======================== + // Member Sizing (NSCP) + // ======================== + + /** + * Size a column per NSCP 410 based on axial load. + * @param {number} Pu_kN - Factored axial load (kN) + * @param {number} height_m - Unsupported height (m) + * @param {object} params - Material/default parameters + * @param {number} params.fc - Concrete f'c (MPa) + * @param {number} params.fy - Steel fy (MPa) + * @param {number} params.defaultColumnB - User override width (mm), 0=auto + * @param {number} params.defaultColumnH - User override depth (mm), 0=auto + * @param {number} params.concreteDensity - kN/m3 + * @returns {object} { b, h, Ast, selfWeight_kN, isOverride, capacityRatio, slendernessRatio, isSlender, Pn_max_kN } + */ + function sizeColumn(Pu_kN, height_m, params) { + const phi = 0.65; // NSCP reduction factor for tied columns + const rho = 0.01; // 1% minimum steel ratio (NSCP 410.6.1.1) + const fc = params.fc; + const fy = params.fy; + const Pu = Pu_kN * 1000; // Convert kN to N + + let b, h; + let isOverride = false; + + // Check for user override (rectangular columns) + if (params.defaultColumnB > 0) { + b = params.defaultColumnB; + h = params.defaultColumnH > 0 ? params.defaultColumnH : b; + isOverride = true; + } else { + // Required gross area (mm²) per NSCP 410.3.5.2 + const Ag_required = Pu / (phi * 0.80 * (0.85 * fc * (1 - rho) + fy * rho)); + + // Size as square column, round up to nearest 50mm + let side = Math.ceil(Math.sqrt(Math.max(0, Ag_required)) / 50) * 50; + const codeMinimumSide = Number(params.minColumnSideMm) > 0 ? Number(params.minColumnSideMm) : 200; + const practicalMinimumSide = Number(params.practicalColumnSideMm) > 0 ? Number(params.practicalColumnSideMm) : 300; + // Auto sizing respects both: code minimum as the hard floor, practical + // residential minimum as the normal baseline, then axial demand above that. + side = Math.max(side, codeMinimumSide, practicalMinimumSide); + b = side; + h = side; + } + + // Actual gross area and required steel + const Ag_actual = b * h; + const Ast = Math.ceil(rho * Ag_actual); + + // Capacity check (DCR) + const Pn_max = phi * 0.80 * (0.85 * fc * (Ag_actual - Ast) + fy * Ast) / 1000; // kN + const capacityRatio = Pn_max > 0 ? Pu_kN / Pn_max : 0; + + // Slenderness ratio check (klu/r) + const lu = height_m * 1000; + const r = Math.min(b, h) * 0.3; + const k = 1.0; + const slendernessRatio = r > 0 ? (k * lu) / r : 0; + + // Column self-weight + const volume_m3 = (b / 1000) * (h / 1000) * height_m; + const selfWeight_kN = volume_m3 * params.concreteDensity; + + return { + b, h, Ast, selfWeight_kN, isOverride, + capacityRatio, + slendernessRatio, + isSlender: slendernessRatio > 22, + Pn_max_kN: Pn_max + }; + } + + /** + * Size a beam per NSCP Table 409.3.1.1 (span-to-depth ratio). + * @param {number} span_m - Beam span (m) + * @param {boolean} isCantilever - Cantilever beam flag + * @param {object} params - Default sizing parameters + * @param {number} params.defaultBeamB - User override width (mm), 0=auto + * @param {number} params.defaultBeamH - User override depth (mm), 0=auto + * @returns {object} { b, h, aspectRatio, spanToDepthRatio, isAdequate } + */ + function sizeBeam(span_m, isCantilever, params) { + let b, h; + + if (params.defaultBeamH > 0) { + h = params.defaultBeamH; + b = params.defaultBeamB || 250; + } else { + const L = span_m * 1000; + const minDepthRatio = isCantilever ? 8 : 16; + h = Math.ceil((L / minDepthRatio) / 50) * 50; + h = Math.max(h, 300); + + b = params.defaultBeamB > 0 + ? params.defaultBeamB + : Math.max(200, Math.ceil((h * 0.4) / 50) * 50); + } + + const aspectRatio = h / b; + const spanToDepthRatio = (span_m * 1000) / h; + + return { + b, h, + aspectRatio, + spanToDepthRatio, + isAdequate: spanToDepthRatio <= (isCantilever ? 8 : 16) + }; + } + + /** + * Apply sizing to all columns and beams. Mutates in-place. + * @param {Array} columns - Column objects + * @param {Array} beams - Beam objects + * @param {object} params - Material/default parameters (fc, fy, defaultColumn/BeamB/H, concreteDensity, floorHeight) + * @returns {object} { totalColumnSelfWeight, totalBeamSelfWeight } + */ + function sizeMembers(columns, beams, params) { + const positiveNumber = value => { + const n = Number(value); + return Number.isFinite(n) && n > 0 ? n : null; + }; + + let totalColumnSelfWeight = 0; + for (let col of columns) { + const activeOnAnyFloor = Array.isArray(params.floors) && params.floors.length + ? params.floors.some(floor => isColumnActiveOnFloor(col, floor.id, params.floors)) + : col.active !== false; + if (!activeOnAnyFloor) continue; + const sizing = sizeColumn(col.totalLoad, params.floorHeight, params); + const manualB = positiveNumber(col.webB) || positiveNumber(col.overrideB) || positiveNumber(col.b); + const manualH = positiveNumber(col.webD) || positiveNumber(col.overrideH) || positiveNumber(col.h); + const b = manualB || sizing.b; + const h = manualH || manualB || sizing.h; + + col.suggestedB = b; + col.suggestedH = h; + col.suggestedAst = sizing.Ast; + col.selfWeight = params.concreteDensity * (b / 1000) * (h / 1000) * params.floorHeight; + col.isOverride = sizing.isOverride || !!manualB || !!manualH; + totalColumnSelfWeight += col.selfWeight; + } + + let totalBeamSelfWeight = 0; + for (let beam of beams) { + const sizing = sizeBeam(beam.span, beam.isCantilever || false, params); + const manualB = positiveNumber(beam.webW) || positiveNumber(beam.overrideB) || positiveNumber(beam.b); + const manualH = positiveNumber(beam.webD) || positiveNumber(beam.overrideH) || positiveNumber(beam.h); + const b = manualB || sizing.b; + const h = manualH || sizing.h; + + beam.suggestedB = b; + beam.suggestedH = h; + + const bM = b / 1000; + const hM = h / 1000; + beam.selfWeight = params.concreteDensity * bM * hM * beam.span; + beam.selfWeightPerM = params.concreteDensity * bM * hM; + totalBeamSelfWeight += beam.selfWeight; + } + + return { totalColumnSelfWeight, totalBeamSelfWeight }; + } + + // ======================== + // Load Calculations + // ======================== + + /** + * Calculate beam end reactions (simply supported / cantilever). + * Mutates beam.Rleft and beam.Rright in-place. + * @param {Array} beams - Beam objects (must have .w, .span set) + */ + function calculateBeamReactions(beams) { + for (let beam of beams) { + if (beam.isCantilever) { + const totalLoad = beam.w * beam.span; + if (beam.startCol) { + beam.Rleft = totalLoad; + beam.Rright = 0; + } else { + beam.Rleft = 0; + beam.Rright = totalLoad; + } + } else if (beam.isEdgeBeam) { + beam.Rleft = beam.w * beam.span / 2; + beam.Rright = beam.w * beam.span / 2; + } else { + beam.Rleft = beam.w * beam.span / 2; + beam.Rright = beam.w * beam.span / 2; + } + } + } + + /** + * Accumulate beam reactions + beam self-weight DL onto columns for one floor. + * Mutates columns in-place (col.floorLoads, col.totalLoad, col.loadPerFloor). + * @param {Array} columns - Column objects + * @param {Array} beams - Beam objects (must have Rleft/Rright/selfWeight set) + * @param {Array} floors - Floors array (for helper lookups) + * @param {string} floorId - Current floor ID + */ + function calculateColumnLoadsForFloor(columns, beams, floors, floorId) { + for (let col of columns) { + if (col.startFloor && !isFloorAtOrAbove(floors, floorId, col.startFloor)) { + col.floorLoads.push({ floorId, load: 0, isPlanted: true }); + continue; + } + + if (!isColumnActiveOnFloor(col, floorId, floors)) { + col.floorLoads.push({ floorId, load: 0, isInactive: true }); + continue; + } + + let floorLoad = 0; + let beamDL = 0; + for (let beam of beams) { + if (beam.startCol === col.id) floorLoad += beam.Rleft; + if (beam.endCol === col.id) floorLoad += beam.Rright; + + if (beam.selfWeight) { + const halfWeight = beam.selfWeight / 2 * 1.2; + if (beam.startCol === col.id) beamDL += halfWeight; + if (beam.endCol === col.id) beamDL += halfWeight; + } + } + const totalFloorLoad = floorLoad + beamDL; + col.floorLoads.push({ floorId, load: totalFloorLoad, slabLoad: floorLoad, beamDL: beamDL }); + col.totalLoad += totalFloorLoad; + col.loadPerFloor = totalFloorLoad; + } + } + + /** + * Calculate footing sizes for all columns. Mutates columns in-place. + * @param {Array} columns - Column objects (must have suggestedB/H, totalLoad set) + * @param {object} params - Configuration object + * @param {number} params.soilBearing - Allowable soil bearing (kPa) + * @param {number} params.numFloors - Total number of floors + * @param {number} params.floorHeight - First floor height (m) + * @param {number} params.concreteDensity - kN/m3 + * @param {Array} params.xSpans - X bay widths + * @param {Array} params.ySpans - Y bay widths + * @returns {object} { tieBeamW, tieBeamH } - Calculated tie beam dimensions + */ + function calculateFootingSizes(columns, params) { + const q = params.soilBearing; + + // Tie beam sizing from longest span unless manually set in the app. + const longestSpan = Math.max(...params.xSpans, ...params.ySpans); + const manualTieBeamW = Number(params.tieBeamWidth); + const manualTieBeamH = Number(params.tieBeamDepth); + const tieBeamH = Number.isFinite(manualTieBeamH) && manualTieBeamH > 0 + ? manualTieBeamH / 1000 + : Math.max(0.3, Math.ceil(longestSpan / 10 * 20) / 20); + const tieBeamW = Number.isFinite(manualTieBeamW) && manualTieBeamW > 0 + ? manualTieBeamW / 1000 + : 0.25; + + // Tie beam DL per column + const avgSpan = (params.xSpans.reduce((a, b) => a + b, 0) + params.ySpans.reduce((a, b) => a + b, 0)) / + (params.xSpans.length + params.ySpans.length); + const tieBeamVolume = tieBeamW * tieBeamH * avgSpan; + const tieBeamWeight = tieBeamVolume * params.concreteDensity; + const tieBeamDLPerColumn = tieBeamWeight * 1.2; + + const foundationFloorId = Array.isArray(params.floors) ? params.floors[0]?.id : null; + for (let col of columns) { + const activeAtFoundation = foundationFloorId + ? isColumnActiveOnFloor(col, foundationFloorId, params.floors) + : col.active !== false; + if (!activeAtFoundation) { + col.footingSize = 0; + col.footingThick = 0; + continue; + } + + if (col.startFloor) { + col.footingSize = 0; + col.footingThick = 0; + col.isPlanted = true; + continue; + } + + const colB = (col.suggestedB || 250) / 1000; + const colH = (col.suggestedH || 250) / 1000; + const colVolume = colB * colH * params.floorHeight; + const colDL = colVolume * params.concreteDensity * params.numFloors * 1.2; + + const totalFactored = col.totalLoad + colDL + tieBeamDLPerColumn; + col.totalLoadWithDL = totalFactored; + + const P_service = totalFactored / 1.4; + const A_req = P_service / q; + + let side = Math.sqrt(A_req); + side = Math.max(0.6, Math.ceil(side * 10) / 10); + + const fixedThicknessMm = Number(params.fixedFootingThicknessMm); + const thick = Number.isFinite(fixedThicknessMm) && fixedThicknessMm > 0 + ? Math.max(0.3, fixedThicknessMm / 1000) + : Math.max(0.3, Math.round(side / 4 * 10) / 10); + + col.footingSize = side; + col.footingThick = thick; + col.columnDL = colDL; + col.tieBeamDL = tieBeamDLPerColumn; + + const footingVolume = side * side * thick; + col.footingDL = footingVolume * params.concreteDensity * 1.2; + } + + return { tieBeamW, tieBeamH }; + } + + /** + * Structural design of a single isolated footing per ACI 318-14. + * Checks punching shear, wide-beam shear, and flexural reinforcement. + * Mutates col.footingDesign and col.footingThick in-place. + * @param {object} col - Column object (must have footingSize, totalLoadWithDL, suggestedB/H) + * @param {object} params - { fc, fy } + * @returns {object|undefined} footingDesign result, or undefined if no footing + */ + function designFooting(col, params) { + if (!col.footingSize || col.footingSize <= 0) return; + + const fc = params.fc || 21; + const fy = params.fy || 415; + const phi_v = 0.75; + const phi_b = 0.90; + const cover = 75; + const barDia = 16; + const Pu = col.totalLoadWithDL || col.totalLoad || 0; + const colB = (col.suggestedB || 250) / 1000; + const colH = (col.suggestedH || 250) / 1000; + const fixedThicknessMm = Number(params.fixedFootingThicknessMm); + const widthFirst = !!params.preferFootingWidthOverDepth && Number.isFinite(fixedThicknessMm) && fixedThicknessMm >= 300; + + function evaluateShear(side, d) { + const qu = Pu / (side * side); + const d_m = d / 1000; + const bo = 2 * ((colB + d_m) + (colH + d_m)); + const Ap = (colB + d_m) * (colH + d_m); + const Vu_punch = Pu - qu * Ap; + const beta_c = Math.max(colH, colB) / Math.min(colH, colB); + const lambda = 1.0; + const Vc1 = 0.33 * lambda * Math.sqrt(fc) * bo * d / 1000; + const Vc2 = (0.17 * (1 + 2 / beta_c)) * lambda * Math.sqrt(fc) * bo * d / 1000; + const alpha_s = 40; + const Vc3 = (0.083 * (alpha_s * d / (bo * 1000) + 2)) * lambda * Math.sqrt(fc) * bo * d / 1000; + const Vc = Math.min(Vc1, Vc2, Vc3); + const phiVc = phi_v * Vc; + + const cantilever = Math.max(0, (side - colB) / 2); + const Vu_wide = Math.max(0, qu * side * (cantilever - d_m)); + const Vc_wide = 0.17 * Math.sqrt(fc) * (side * 1000) * d / 1000; + const phiVc_wide = phi_v * Vc_wide; + + return { + qu, + punchingVu: Vu_punch, + punchingPhiVc: phiVc, + punchingOK: phiVc >= Vu_punch, + wideVu: Vu_wide, + widePhiVc: phiVc_wide, + wideOK: phiVc_wide >= Vu_wide + }; + } + + let L = col.footingSize; + let hFinal; + let dFinal; + let shear; + let widenedForFixedDepth = false; + + if (widthFirst) { + hFinal = fixedThicknessMm; + dFinal = hFinal - cover - barDia / 2; + shear = evaluateShear(L, dFinal); + // Keep residential preliminary footing size governed by soil bearing. + // If 300mm thickness does not satisfy shear, report it instead of + // silently creating an impractically wide footing. + } else { + let d = (col.footingThick || 0.3) * 1000 - cover - barDia / 2; + + for (let iter = 0; iter < 5; iter++) { + shear = evaluateShear(L, d); + if (shear.punchingOK) break; + d += 25; + } + + shear = evaluateShear(L, d); + while (!shear.wideOK && d < 1500) { + d += 25; + shear = evaluateShear(L, d); + } + + const hReq = d + cover + barDia / 2; + hFinal = Math.max(300, Math.ceil(hReq / 50) * 50); + dFinal = hFinal - cover - barDia / 2; + shear = evaluateShear(L, dFinal); + } + + // Flexural reinforcement + const qu = shear.qu; + const Mu_total = qu * L * Math.pow((L - colB) / 2, 2) / 2; + const b_mm = L * 1000; + const Rn = (Mu_total * 1e6) / (phi_b * b_mm * dFinal * dFinal); + + const discriminant = 1 - (2 * Rn) / (0.85 * fc); + let rho; + if (discriminant > 0) { + rho = (0.85 * fc / fy) * (1 - Math.sqrt(discriminant)); + } else { + rho = 0.85 * fc / fy * 0.5; + } + + const rho_min = fy >= 400 ? 0.0018 : 0.0020; + rho = Math.max(rho, rho_min); + + const As_req = rho * b_mm * dFinal; + const Ab = Math.PI * barDia * barDia / 4; + const nBars = Math.max(2, Math.ceil(As_req / Ab)); + const spacing = Math.floor((b_mm - 2 * cover) / (nBars - 1)); + + col.footingDesign = { + L: L, + h: hFinal, + d: dFinal, + qu: qu.toFixed(1), + punchingVu: shear.punchingVu.toFixed(1), + punchingPhiVc: shear.punchingPhiVc.toFixed(1), + punchingOK: shear.punchingOK, + wideVu: shear.wideVu.toFixed(1), + widePhiVc: shear.widePhiVc.toFixed(1), + wideOK: shear.wideOK, + Mu: Mu_total.toFixed(1), + rho: (rho * 100).toFixed(3), + As: As_req.toFixed(0), + nBars: nBars, + barDia: barDia, + spacing: spacing, + rebarStr: nBars + '-ø' + barDia + 'mm @ ' + spacing + 'mm c/c EW', + thicknessPolicy: widthFirst ? '300mm fixed residential preliminary; bearing-sized with shear review' : 'depth-by-shear preliminary', + widenedForFixedDepth, + requiresDepthReview: widthFirst && !(shear.punchingOK && shear.wideOK) + }; + + col.footingThick = hFinal / 1000; + + return col.footingDesign; + } + + // ======================== + // Public API + // ======================== + + return { + isFloorAtOrAbove: isFloorAtOrAbove, + isColumnActiveOnFloor: isColumnActiveOnFloor, + sizeColumn: sizeColumn, + sizeBeam: sizeBeam, + sizeMembers: sizeMembers, + calculateBeamReactions: calculateBeamReactions, + calculateColumnLoadsForFloor: calculateColumnLoadsForFloor, + calculateFootingSizes: calculateFootingSizes, + designFooting: designFooting + }; + +})(); diff --git a/v3/engine/tributary.js b/v3/engine/tributary.js new file mode 100644 index 0000000..3149629 --- /dev/null +++ b/v3/engine/tributary.js @@ -0,0 +1,178 @@ +/** + * engine/tributary.js — Tributary area helpers + * + * First extraction wave focuses on the pure custom-beam slab strip math so + * the monolith can delegate the lowest-risk tributary calculation first. + */ +'use strict'; + +const EngineTributary = (function () { + + function getOverlapLength(start, end, min, max) { + return Math.max(0, Math.min(end, max) - Math.max(start, min)); + } + + function getPolygonCentroid(poly) { + let cx = 0; + let cy = 0; + const points = Array.isArray(poly) ? poly : []; + + if (points.length === 0) { + return { cx: 0, cy: 0 }; + } + + for (const pt of points) { + cx += pt.x; + cy += pt.y; + } + + return { + cx: cx / points.length, + cy: cy / points.length + }; + } + + /** + * Calculate the slab strip area tributary to a custom beam crossing a slab. + * The beam receives load from both sides up to the 45-degree limit or slab edge. + * @param {object} crossingBeam - Custom beam crossing descriptor + * @param {object} slab - Slab geometry + * @returns {number} + */ + function calculateCustomBeamTributaryArea(crossingBeam, slab) { + if (!crossingBeam || !slab) return 0; + + if (crossingBeam.splitDir === 'Y') { + const tributaryWidthTop = Math.min( + Math.max(0, crossingBeam.splitPos - slab.y1), + slab.lx / 2 + ); + const tributaryWidthBottom = Math.min( + Math.max(0, slab.y2 - crossingBeam.splitPos), + slab.lx / 2 + ); + const beamLengthInSlab = getOverlapLength(crossingBeam.start, crossingBeam.end, slab.x1, slab.x2); + return beamLengthInSlab * (tributaryWidthTop + tributaryWidthBottom); + } + + const tributaryWidthLeft = Math.min( + Math.max(0, crossingBeam.splitPos - slab.x1), + slab.ly / 2 + ); + const tributaryWidthRight = Math.min( + Math.max(0, slab.x2 - crossingBeam.splitPos), + slab.ly / 2 + ); + const beamLengthInSlab = getOverlapLength(crossingBeam.start, crossingBeam.end, slab.y1, slab.y2); + return beamLengthInSlab * (tributaryWidthLeft + tributaryWidthRight); + } + + /** + * Resolve the supporting grid beam and adjacent cantilever beams for a cantilever slab. + * Prefers the slab's stored supportingBeamId and falls back to canonical ID construction. + * @param {object} slab + * @param {object} counts + * @param {number} counts.xSpanCount + * @param {number} counts.ySpanCount + * @returns {object} { mainBeamId, cantileverBeamIds } + */ + function getCantileverSupportBeamIds(slab, counts) { + const edge = slab?.cantileverEdge; + const spanIndex = Number(slab?.spanIndex) || 0; + const grid = counts || {}; + let mainBeamId = slab?.supportingBeamId || null; + + if (!mainBeamId) { + if (edge === 'top') { + mainBeamId = `BX-1-${spanIndex + 1}`; + } else if (edge === 'bottom') { + mainBeamId = `BX-${(grid.ySpanCount || 0) + 1}-${spanIndex + 1}`; + } else if (edge === 'left') { + mainBeamId = `BY-1-${spanIndex + 1}`; + } else if (edge === 'right') { + mainBeamId = `BY-${(grid.xSpanCount || 0) + 1}-${spanIndex + 1}`; + } + } + + let cantileverBeamIds = []; + if (edge === 'top' || edge === 'bottom') { + const prefix = edge === 'top' ? 'BCY-T' : 'BCY-B'; + cantileverBeamIds = [`${prefix}-${spanIndex + 1}`, `${prefix}-${spanIndex + 2}`]; + } else if (edge === 'left' || edge === 'right') { + const prefix = edge === 'left' ? 'BCX-L' : 'BCX-R'; + cantileverBeamIds = [`${prefix}-${spanIndex + 1}`, `${prefix}-${spanIndex + 2}`]; + } + + return { mainBeamId, cantileverBeamIds }; + } + + /** + * Build a beam slice record with computed load-per-meter and polygon centroid. + * @param {object} options + * @param {string} options.slabId + * @param {string} options.side + * @param {number} options.area + * @param {number} options.pu + * @param {number} options.span + * @param {Array} options.poly + * @param {number} [options.wOverride] + * @param {object} [options.extra] + * @returns {object} + */ + function createBeamSlice(options) { + const opts = options || {}; + const area = opts.area || 0; + const span = opts.span || 0; + const poly = Array.isArray(opts.poly) ? opts.poly : []; + const centroid = getPolygonCentroid(poly); + const tributaryWidth = span > 0 ? area / span : 0; + const w = opts.wOverride !== undefined ? opts.wOverride : (opts.pu || 0) * tributaryWidth; + + const slice = { + slabId: opts.slabId, + side: opts.side, + area: area, + w: w + }; + + if (opts.extra) { + Object.assign(slice, opts.extra); + } + + slice.poly = poly; + slice.cx = centroid.cx; + slice.cy = centroid.cy; + + return slice; + } + + /** + * Resolve final tributary width and factored line load for a beam. + * @param {object} beam + * @param {number} pu - Factored slab load per square meter + * @param {number} wallLoad - Unfactored wall line load (kN/m) + * @returns {object} { tributaryArea, tributaryWidth, wallLoad, w } + */ + function calculateBeamLineLoad(beam, pu, wallLoad) { + const tributaryArea = Math.max(0, beam?.tributaryArea || 0); + const span = beam?.span || 0; + const tributaryWidth = span > 0 ? tributaryArea / span : 0; + const slabLoad = (pu || 0) * tributaryWidth; + const factoredWallLoad = beam?.isCantilever || beam?.isEdgeBeam ? 0 : 1.2 * (wallLoad || 0); + + return { + tributaryArea: tributaryArea, + tributaryWidth: tributaryWidth, + wallLoad: factoredWallLoad, + w: slabLoad + factoredWallLoad + }; + } + + return { + calculateCustomBeamTributaryArea: calculateCustomBeamTributaryArea, + getCantileverSupportBeamIds: getCantileverSupportBeamIds, + createBeamSlice: createBeamSlice, + calculateBeamLineLoad: calculateBeamLineLoad + }; + +})(); diff --git a/v3/index.html b/v3/index.html index 00a2984..b34cc86 100644 --- a/v3/index.html +++ b/v3/index.html @@ -5,6 +5,17 @@ FutolStructure | Structural Engineering + + + + + + + + + + + - - - - + @media (max-width: 800px) { + .header { + padding: 0 8px; + } + + .header-actions { + display: none; + } + + .app-container { + grid-template-columns: minmax(0, 1fr); + height: calc(100vh - 44px - 24px); + } + + .app-container > .panel { + display: none; + } + + .canvas-container { + width: 100%; + min-width: 0; + } + + .canvas-toolbar { + max-height: 148px; + overflow-y: auto; + } + + .display-3d-panel { + width: calc(100% - 20px); + max-width: 300px; + } + + .status-bar { + overflow-x: auto; + white-space: nowrap; + } + } + + + + + +
-
+
+ + + + - + + QA +
@@ -1369,6 +1872,13 @@

Floors

+
@@ -1469,31 +1979,31 @@

Material & Member Sizing

- +
- +
- +
- +
- Set to 0 for auto-sizing per NSCP rules. Column h=0 means square. + B is plan X/width. H/D is plan Y/depth. Set to 0 for auto-sizing per NSCP rules.
@@ -1511,15 +2021,26 @@

Footing Parameters

- +
- +
+
+ + +
+ Footings and tie beams enabled +
+
@@ -1564,14 +2085,17 @@

Cantilevers (m)

- - + -
@@ -1597,8 +2121,8 @@

Cantilevers (m)

- - + + Design @@ -1606,7 +2130,7 @@

Cantilevers (m)

- + @@ -1636,8 +2160,9 @@

Cantilevers (m)

+ - + @@ -1647,17 +2172,52 @@

Cantilevers (m)

+ + + + + - + - + + +
-
+
+ + +
@@ -1674,8 +2234,8 @@

📋 Column Schedule

No. ID Mark - B (mm) - H (mm) + B/X (mm) + H/Y (mm) Load (kN) Footing ID @@ -1701,12 +2261,14 @@

📋 Beam Schedule

No. ID + Type Dir Span (m) - B (mm) - H (mm) + B/X (mm) + H/Y (mm) w (kN/m) R (kN) + Action @@ -1742,6 +2304,7 @@

📋 Footing Schedule

+
@@ -1860,6 +2423,16 @@

📋 Slab Schedule

+
+
+ Model Readiness + 0 warnings +
+
    +
  • Run analysis to refresh model readiness warnings.
  • +
+
+ - + @@ -2475,6 +3095,42 @@

Project Dashboard

+ +