first commit

This commit is contained in:
2026-07-22 13:48:46 +08:00
commit c87751c3dc
2820 changed files with 726976 additions and 0 deletions
@@ -0,0 +1,4 @@
*
!.gitignore
!skills/
!skills/**
@@ -0,0 +1,166 @@
---
name: simplecad-self-evolve
description: Thin SimpleCAD skill that installs runtime SDK from PyPI into current venv site-packages, then provides deterministic REPL/Jupyter usage and references.
license: MIT
compatibility: Requires Python 3.10+, active virtual environment, and network access for package installation.
metadata:
project: simplecadapi
version: 2.0.8
runtime-package: simplecadapi
runtime-spec: simplecadapi==2.0.8
cases-module: simplecad_self_evolve_cases
---
# SimpleCAD Runtime Skill
## Philosophy
- This is a thin skill package: docs + scripts only.
- SDK source code is not bundled in this skill.
- Runtime code is installed from PyPI into active virtual environment site-packages.
- Skill-local evolved cases are stored under `cases/simplecad_self_evolve_cases/`.
## Working From Repo Root
- Tool calls run from the repo root.
- Use one explicit skill root: `./skills/simplecad-self-evolve/` or `./workspace/skills/simplecad-self-evolve/`.
- Main doc paths:
- `<skill_root>/SKILL.md`
- `<skill_root>/references/docs/api/README.md`
- `<skill_root>/references/docs/api/<api_name>.md`
- `<skill_root>/references/docs/core/<type_name>.md`
- Skill layout also includes `<skill_root>/scripts/` and `<skill_root>/cases/`.
## MUST Requirements
1. Read `SKILL.md` and `references/docs/api/README.md` before choosing APIs.
2. Read the exact API Markdown page for every API you use.
3. Read the needed `core/` and tag/selection docs when an API needs `Edge`, `Face`, `Wire`, `Solid`, `Assembly`, or tags.
4. Follow the documented API signatures exactly.
5. Use geometry APIs for integrated parts and declarative constraints for final assemblies.
6. Use tags consistently.
7. Build and validate incrementally. Each step MUST include a small grounding `print`, and grounding MUST use QL where possible.
8. For inspection/debugging, query geometry with QL and print only the queried facts you need; do not print whole solids, assemblies, or full model objects.
9. Boolean operations always return `List[Solid]`. You MUST check `len(results)` before using `results[0]`.
10. `union_rsolidlist(...)` already uses SimpleCAD's tuned default boolean settings internally. Do not add manual boolean tuning unless you are debugging a stubborn edge case.
11. If tangent-only contact leaves multiple solids after `union_rsolidlist(...)`, that is often acceptable. Keep the list and continue operating on the list or iterate over its solids.
12. If the design explicitly requires exactly one merged solid and `len(results) != 1`, you MUST NOT silently pick one item. Instead, slightly adjust part placement so the intended bodies overlap/embed, run the union again, and only then unwrap the single result.
13. After model construction, ask the user whether the result is satisfactory and whether any modifications are needed. Only after explicit user confirmation may you add the script to evolve cases.
## Boolean result discipline
- `union_rsolidlist(...)`, `cut_rsolidlist(...)`, and `intersect_rsolidlist(...)` accept mixed inputs: standalone `Solid`, lists of `Solid`, and nested sequences.
- They always return `List[Solid]`.
- `union_rsolidlist(...)` already applies the package's default glue mode and a conservative internal tolerance.
- If a union still returns multiple solids that remain separated beyond tolerance, the API prints a stdout warning automatically.
- Default behavior: keep the list result and pass it forward or iterate over it.
- Only unwrap to a single solid after an explicit `len(results) == 1` check.
- If a single merged solid is required but a union still returns multiple solids, slightly move the parts so they overlap instead of merely touching, then recompute the union.
## Install behavior
- Preferred: run `scripts/install.sh` once when skill is installed/activated.
- Runtime wrappers auto-install on demand if `simplecadapi` is missing.
- Package installed by default: `simplecadapi==2.0.8`
- Wrappers install only into a virtual environment interpreter (set `PYTHON_BIN` when needed).
## Interpreter selection
Use the interpreter from your active/current venv site-packages. Example:
```bash
PYTHON_BIN=.venv/bin/python scripts/install.sh
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh --check
```
## Skill path activation
To activate this skill path in current shell:
```bash
eval "$(scripts/with_skill.sh --print-env)"
```
This exports `SIMPLECAD_SKILL_ROOT`, `SIMPLECAD_CASES_ROOT`, `SIMPLECAD_CASES_MODULE`, and updates `PYTHONPATH`.
## How to import and use
After runtime install, import normally (no custom `sys.path` needed):
```python
import simplecadapi as scad
from simplecadapi import make_box_rsolid, export_stl
```
Typical usage in a Python script:
```python
import simplecadapi as scad
from simplecadapi import make_box_rsolid, export_stl
shape = make_box_rsolid(10.0, 20.0, 30.0)
export_stl(shape, "example_box.stl")
```
Import skill-local evolved cases:
```python
from simplecad_self_evolve_cases.evolve import my_new_case
```
Run script with wrapper (auto-installs runtime when missing):
```bash
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh -- .venv/bin/python your_script.py
```
Quick import check in current venv:
```bash
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh --check
```
## Self-evolve in skill directory
Add a new case function from a local Python script:
```bash
scripts/add_new_case.sh path/to/new_case.py
```
By default, the first top-level function in that file is appended into:
- `cases/simplecad_self_evolve_cases/evolve.py`
Then import it with:
```python
from simplecad_self_evolve_cases.evolve import your_function_name
```
## Persistent REPL / notebook kernel
In a long-running kernel session, bootstrap once in first cell:
```python
%run ./scripts/repl_bootstrap.py
import simplecadapi as scad
from simplecad_self_evolve_cases.evolve import my_new_case
```
If your kernel also needs notebook tools installed in this environment:
```python
%run ./scripts/repl_bootstrap.py --with-jupyter
```
## Jupyter launch
- `scripts/jupyter_with_skill.sh lab`
- `scripts/jupyter_with_skill.sh notebook`
- This wrapper ensures runtime package and Jupyter deps (`jupyterlab>=4.5.5, ipykernel>=6.29.5`) are available.
## Script quick reference
- `scripts/install.sh`: install runtime package to active venv site-packages.
- `scripts/with_skill.sh`: ensure runtime installed and run any command.
- `scripts/jupyter_with_skill.sh`: launch Jupyter with automatic dependency bootstrapping.
- `scripts/repl_bootstrap.py`: one-time activation helper for persistent Python sessions.
- `scripts/add_new_case.sh`: append new function into skill-local evolve module.
- `scripts/evolve_case.py`: Python extractor used by `add_new_case.sh`.
- `scripts/validate_skill.sh`: validate skill structure.
## References
- `references/PROJECT_OVERVIEW.md`
- `references/RUNTIME_INSTALL.md`
- `references/EVOLVE_WORKFLOW.md`
- `references/docs/api/`
- `references/docs/core/`
- `references/PROJECT_README.md`
@@ -0,0 +1,3 @@
"""Skill-local evolved cases for simplecad-self-evolve."""
from .evolve import *
@@ -0,0 +1,6 @@
"""Skill-local evolved case functions.
This module is managed by `scripts/add_new_case.sh` and `scripts/evolve_case.py`.
"""
__all__: list[str] = []
@@ -0,0 +1,34 @@
# Skill-Local Evolve Workflow
This thin skill does not modify `site-packages/simplecadapi` directly.
New evolve cases are stored in skill-local module:
- `cases/simplecad_self_evolve_cases/evolve.py`
## 1) Add a new case from a Python file
```bash
scripts/add_new_case.sh path/to/new_case.py
```
By default, the first top-level function in `new_case.py` is extracted.
## 2) Activate skill paths
```bash
eval "$(scripts/with_skill.sh --print-env)"
```
## 3) Import and use in Python
```python
import simplecadapi as scad
from simplecad_self_evolve_cases.evolve import your_function_name
```
## 4) Persistent kernel usage
```python
%run ./scripts/repl_bootstrap.py
from simplecad_self_evolve_cases.evolve import your_function_name
```
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,31 @@
# Project Overview
- Project: `simplecadapi`
- Version: `2.0.8`
- Runtime package: `simplecadapi==2.0.8`
- Skill cases module: `simplecad_self_evolve_cases`
## What this skill bundles
- Skill instructions (`SKILL.md`)
- Helper scripts (`scripts/`)
- Documentation references (`references/docs/`)
- Skill-local evolve package (`cases/simplecad_self_evolve_cases/`)
## What this skill does not bundle
- SDK source code (`src/simplecadapi`) is intentionally excluded.
- Runtime code is always resolved from site-packages.
## Runtime bootstrap strategy
0. Select a virtual environment interpreter (`PYTHON_BIN` if needed).
1. Try `import simplecadapi`.
2. If import fails, run `scripts/install.sh`.
3. Activate skill paths (`eval "$(scripts/with_skill.sh --print-env)"`).
4. Import skill-local cases from `simplecad_self_evolve_cases.evolve`.
## Optional Jupyter dependencies
- `jupyterlab>=4.5.5`
- `ipykernel>=6.29.5`
@@ -0,0 +1,216 @@
# SimpleCADAPI
SimpleCADAPI is an imperative CAD modeling Python package based on CADQuery. Its goal is to encapsulate common modeling operations into a clear, composable, testable functional API, and to support distributing "documentation + scripts + runtime installation" workflows via Skills.
## README Scope
This README only covers package-level capabilities, installation methods, publishing/packaging workflows, and Skills usage instructions.
Experimental scripts and temporary modeling examples are not included as formal documentation.
## Package Installation (Python Package Managers)
Current package name: `simplecadapi`, version: `2.0.8` (see `pyproject.toml`).
### Method A: Install from package repository with pip
```bash
pip install simplecadapi
```
Optional development dependencies:
```bash
pip install "simplecadapi[dev]"
```
### Method B: Install with uv
Install in the current virtual environment:
```bash
uv pip install simplecadapi
```
Add as a project dependency in `pyproject.toml`:
```bash
uv add simplecadapi
```
### Method C: Install from local build artifacts
The repository already contains example build artifacts (`dist/`):
```bash
pip install dist/simplecadapi-2.0.8-py3-none-any.whl
```
If you need to rebuild:
```bash
uv build
```
## Quick Verification of Installation
```python
import simplecadapi as scad
box = scad.make_box_rsolid(10.0, 20.0, 30.0)
scad.export_stl(box, "example_box.stl")
scad.export_step(box, "example_box.step")
```
## How to Package and Use Skills
This project provides the `skill-pack` CLI for generating lightweight skill packages (thin mode): **No built-in SDK source code**, runtime installs `simplecadapi` from the package repository.
### 1) Packaging Command
Execute in the repository root directory:
```bash
uv run skill-pack --refresh-docs --archive --skill-name simplecad-self-evolve
```
Common parameters:
- `--output-root <dir>`: Output directory (default `./skills`)
- `--package-name <pkg>`: Runtime installation package name (default reads from `project.name`)
- `--package-version <ver>`: Runtime installation version (default reads from `project.version`)
- `--no-clean`: Do not clean existing output directory
- `--archive`: Additionally generate `<skill-name>.tar.gz`
### 2) Packaging Result Structure
After packaging, you will get a directory similar to:
- `skills/simplecad-self-evolve/SKILL.md`
- `skills/simplecad-self-evolve/scripts/`
- `skills/simplecad-self-evolve/references/`
- `skills/simplecad-self-evolve/cases/simplecad_self_evolve_cases/`
### 3) Install and Verify Runtime in the Skill Directory
```bash
cd skills/simplecad-self-evolve
PYTHON_BIN=.venv/bin/python scripts/install.sh
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh --check
```
### 4) Run Your Program with the Wrapper Script
```bash
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh -- .venv/bin/python your_script.py
```
### 5) Activate skill-local Case Module Path
```bash
eval "$(scripts/with_skill.sh --print-env)"
```
After activation, you can directly import:
```python
from simplecad_self_evolve_cases.evolve import make_involute_spur_gear_rsolid
```
### 6) Add New Functions to the Skill-local evolve Module
```bash
scripts/add_new_case.sh path/to/new_case.py
```
### 7) Jupyter and Structure Validation
```bash
scripts/jupyter_with_skill.sh lab
scripts/validate_skill.sh
```
## Auto Tools
The project includes 4 main CLIs:
- `auto-docs-gen`: Generate `docs/api/` documentation from API source code
- `make-export`: Update imports/exports in `src/simplecadapi/__init__.py`
- `evolve`: Extract functions from scripts and append to the evolve module
- `skill-pack`: Package thin skill (documentation + scripts + cases)
Examples:
```bash
uv run make-export --dry-run
uv run auto-docs-gen
uv run evolve path/to/your_case.py
uv run skill-pack --refresh-docs --archive
```
## RAGFlow Documentation Sync
`scripts/sync_ragflow_docs.py` is used to incrementally sync Markdown files under `docs/` to the specified RAGFlow dataset, chunked by H2 headings; the document's `chunk_method` is set to `manual`.
Prepare the environment:
```bash
.venv/bin/python -m pip install ragflow-sdk
```
It is recommended to use `.env` (already added to `.gitignore`):
```bash
RAGFLOW_API_KEY=your_key_here
RAGFLOW_BASE_URL=http://localhost
RAGFLOW_DATASET_NAME=SimpleCADAPI
```
Run the sync:
```bash
set -a && source .env && set +a
.venv/bin/python scripts/sync_ragflow_docs.py --create-dataset
```
Common parameters:
- `--dataset-id` / `RAGFLOW_DATASET_ID`: Directly specify the dataset ID (avoid name conflicts)
- `--delete-removed`: Delete documents that have been removed locally
- `--dry-run`: Only preview changes without executing writes
- `--progress-interval N`: Print progress every N documents
## Development and Testing
Local development installation (editable):
```bash
uv pip install -e ".[dev]"
```
Run unit tests:
```bash
uv run python -m unittest test/test_all_features.py
```
Run examples:
```bash
uv run python examples.py
```
## Core Design Constraints (Brief)
- API functions uniformly use `snake_case` and reflect return types in function names (e.g., `*_rsolid`, `*_rwire`).
- Core types are kept as stable as possible; functionality is extended by adding new functions (Open-Closed Principle).
- Support `SimpleWorkplane` context for local coordinate modeling.
- Export interfaces support single entities, multiple entities, and nested list inputs.
## Documentation Entry Points
- API documentation: `docs/api/`
- Core documentation: `docs/core/`
## License
MIT, see `LICENSE`.
@@ -0,0 +1,48 @@
# Runtime Install Reference
## Base install
```bash
PYTHON_BIN=.venv/bin/python scripts/install.sh
```
This installs `simplecadapi==2.0.8` to the active Python environment.
If `PYTHON_BIN` is not set, wrappers default to `python3` (fallback `python`).
Installation is intentionally blocked for non-venv/system interpreters.
## Install with Jupyter support
```bash
PYTHON_BIN=.venv/bin/python scripts/install.sh --with-jupyter
```
## Upgrade package
```bash
PYTHON_BIN=.venv/bin/python scripts/install.sh -- --upgrade
```
Everything after `--` is forwarded to `uv pip install` (or `python -m pip install` fallback).
## Validate runtime
```bash
PYTHON_BIN=.venv/bin/python scripts/with_skill.sh --check
.venv/bin/python scripts/repl_bootstrap.py --check
```
## Activate skill paths in current shell
```bash
eval "$(scripts/with_skill.sh --print-env)"
```
## Add and import skill-local evolved case
```bash
scripts/add_new_case.sh path/to/new_case.py
```
```python
from simplecad_self_evolve_cases.evolve import your_function_name
```
@@ -0,0 +1,119 @@
# SimpleCAD API Index
This index includes API docs generated from `operations.py`, `evolve.py`, `constraints.py`, and `ql.py`.
## Basic Creation
- [make_angle_arc_redge](make_angle_arc_redge.md) *(from operations.py)*
- [make_angle_arc_rwire](make_angle_arc_rwire.md) *(from operations.py)*
- [make_box_rscalarfield](make_box_rscalarfield.md) *(from field.py)*
- [make_box_rsolid](make_box_rsolid.md) *(from operations.py)*
- [make_capsule_rscalarfield](make_capsule_rscalarfield.md) *(from field.py)*
- [make_circle_redge](make_circle_redge.md) *(from operations.py)*
- [make_circle_rface](make_circle_rface.md) *(from operations.py)*
- [make_circle_rwire](make_circle_rwire.md) *(from operations.py)*
- [make_cone_rsolid](make_cone_rsolid.md) *(from operations.py)*
- [make_cylinder_rsolid](make_cylinder_rsolid.md) *(from operations.py)*
- [make_ellipsoid_rscalarfield](make_ellipsoid_rscalarfield.md) *(from field.py)*
- [make_face_from_wire_rface](make_face_from_wire_rface.md) *(from operations.py)*
- [make_field_surface_rsolid](make_field_surface_rsolid.md) *(from operations.py)*
- [make_helix_redge](make_helix_redge.md) *(from operations.py)*
- [make_helix_rwire](make_helix_rwire.md) *(from operations.py)*
- [make_line_redge](make_line_redge.md) *(from operations.py)*
- [make_point_rvertex](make_point_rvertex.md) *(from operations.py)*
- [make_polyline_rwire](make_polyline_rwire.md) *(from operations.py)*
- [make_rectangle_rface](make_rectangle_rface.md) *(from operations.py)*
- [make_rectangle_rwire](make_rectangle_rwire.md) *(from operations.py)*
- [make_segment_redge](make_segment_redge.md) *(from operations.py)*
- [make_segment_rwire](make_segment_rwire.md) *(from operations.py)*
- [make_sphere_rscalarfield](make_sphere_rscalarfield.md) *(from field.py)*
- [make_sphere_rsolid](make_sphere_rsolid.md) *(from operations.py)*
- [make_spline_redge](make_spline_redge.md) *(from operations.py)*
- [make_spline_rwire](make_spline_rwire.md) *(from operations.py)*
- [make_three_point_arc_redge](make_three_point_arc_redge.md) *(from operations.py)*
- [make_three_point_arc_rwire](make_three_point_arc_rwire.md) *(from operations.py)*
- [make_wire_from_edges_rwire](make_wire_from_edges_rwire.md) *(from operations.py)*
## Transforms
- [mirror_shape](mirror_shape.md) *(from operations.py)*
- [rotate_rscalarfield](rotate_rscalarfield.md) *(from field.py)*
- [rotate_shape](rotate_shape.md) *(from operations.py)*
- [translate_rscalarfield](translate_rscalarfield.md) *(from field.py)*
- [translate_shape](translate_shape.md) *(from operations.py)*
## 3D Operations
- [extrude_rsolid](extrude_rsolid.md) *(from operations.py)*
- [loft_rsolid](loft_rsolid.md) *(from operations.py)*
- [revolve_rsolid](revolve_rsolid.md) *(from operations.py)*
- [sweep_rsolid](sweep_rsolid.md) *(from operations.py)*
## Tagging and Selection
- [select_edges_by_tag](select_edges_by_tag.md) *(from operations.py)*
- [select_faces_by_tag](select_faces_by_tag.md) *(from operations.py)*
- [set_tag](set_tag.md) *(from operations.py)*
## Boolean Operations
- [cut_rsolidlist](cut_rsolidlist.md) *(from operations.py)*
- [intersect_rscalarfield](intersect_rscalarfield.md) *(from field.py)*
- [intersect_rsolidlist](intersect_rsolidlist.md) *(from operations.py)*
- [union_rscalarfield](union_rscalarfield.md) *(from field.py)*
- [union_rsolidlist](union_rsolidlist.md) *(from operations.py)*
## Export
- [export_step](export_step.md) *(from operations.py)*
- [export_stl](export_stl.md) *(from operations.py)*
## Advanced Features
- [chamfer_rsolid](chamfer_rsolid.md) *(from operations.py)*
- [fillet_rsolid](fillet_rsolid.md) *(from operations.py)*
- [helical_sweep_rsolid](helical_sweep_rsolid.md) *(from operations.py)*
- [shell_rsolid](shell_rsolid.md) *(from operations.py)*
## Evolve
- [make_n_hole_flange_rsolid](make_n_hole_flange_rsolid.md) *(from evolve.py)*
- [make_naca_propeller_blade_rsolid](make_naca_propeller_blade_rsolid.md) *(from evolve.py)*
- [make_threaded_rod_rsolid](make_threaded_rod_rsolid.md) *(from evolve.py)*
## Assembly Constraints
- [add_part_rassembly](add_part_rassembly.md) *(from constraints.py)*
- [clear_constraints_rassembly](clear_constraints_rassembly.md) *(from constraints.py)*
- [clone_assembly_rassembly](clone_assembly_rassembly.md) *(from constraints.py)*
- [constrain_coincident_rassembly](constrain_coincident_rassembly.md) *(from constraints.py)*
- [constrain_concentric_rassembly](constrain_concentric_rassembly.md) *(from constraints.py)*
- [constrain_distance_rassembly](constrain_distance_rassembly.md) *(from constraints.py)*
- [constrain_offset_rassembly](constrain_offset_rassembly.md) *(from constraints.py)*
- [make_assembly_rassembly](make_assembly_rassembly.md) *(from constraints.py)*
- [rotate_part_rassembly](rotate_part_rassembly.md) *(from constraints.py)*
- [solve_assembly_rresult](solve_assembly_rresult.md) *(from constraints.py)*
- [stack](stack.md) *(from constraints.py)*
- [stack_rassembly](stack_rassembly.md) *(from constraints.py)*
- [translate_part_rassembly](translate_part_rassembly.md) *(from constraints.py)*
## Other
- [and_](and_.md) *(from ql.py)*
- [bounds_rbbox](bounds_rbbox.md) *(from field.py)*
- [eval_rarray](eval_rarray.md) *(from field.py)*
- [eval_rscalar](eval_rscalar.md) *(from field.py)*
- [geo](geo.md) *(from ql.py)*
- [linear_pattern_rsolidlist](linear_pattern_rsolidlist.md) *(from operations.py)*
- [meta](meta.md) *(from ql.py)*
- [not_](not_.md) *(from ql.py)*
- [or_](or_.md) *(from ql.py)*
- [radial_pattern_rsolidlist](radial_pattern_rsolidlist.md) *(from operations.py)*
- [render_screenshot_rpath](render_screenshot_rpath.md) *(from operations.py)*
- [scale_rscalarfield](scale_rscalarfield.md) *(from field.py)*
- [select](select.md) *(from ql.py)*
- [smooth_subtract_rscalarfield](smooth_subtract_rscalarfield.md) *(from field.py)*
- [smooth_union_rscalarfield](smooth_union_rscalarfield.md) *(from field.py)*
- [subtract_rscalarfield](subtract_rscalarfield.md) *(from field.py)*
- [tag](tag.md) *(from ql.py)*
- [value](value.md) *(from ql.py)*
@@ -0,0 +1,13 @@
# add_part_rassembly
## API Definition
```python
def add_part_rassembly(assembly: Assembly, name: str, solid: Solid, parent: Optional[Union[str, PartHandle]] = None, local_transform: Optional[Union[np.ndarray, Sequence[Sequence[float]]]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a part in assembly space and return a new assembly.
@@ -0,0 +1,25 @@
# and_
## API Definition
```python
def and_(*predicates: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build an AND-composed predicate.
Q.and_(Q.tag("face.top"), Q.tag("role.mounting_surface"))
## Parameters
### *predicates
- **Description**: Any number of predicates.
## Returns
Callable[[Any], bool]: Combined predicate.
@@ -0,0 +1,23 @@
# bounds_rbbox
## API Definition
```python
def bounds_rbbox(field: ScalarField) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]
```
*Source: field.py*
## Description
Compute the axis-aligned bounding box of a scalar field.
## Parameters
### field
- **Description**: Scalar field.
## Returns
Tuple[min_xyz, max_xyz]: Bounding box.
@@ -0,0 +1,13 @@
# chamfer_rsolid
## API Definition
```python
def chamfer_rsolid(solid: Solid, edges: List[Edge], distance: float) -> Solid
```
*Source: operations.py*
## Description
Apply chamfers to selected solid edges.
@@ -0,0 +1,13 @@
# clear_constraints_rassembly
## API Definition
```python
def clear_constraints_rassembly(assembly: Assembly) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: clear constraints and return a new assembly.
@@ -0,0 +1,13 @@
# clone_assembly_rassembly
## API Definition
```python
def clone_assembly_rassembly(assembly: Assembly) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: clone one assembly object into another.
@@ -0,0 +1,13 @@
# constrain_coincident_rassembly
## API Definition
```python
def constrain_coincident_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a coincident constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_concentric_rassembly
## API Definition
```python
def constrain_concentric_rassembly(assembly: Assembly, reference: AxisAnchor, moving: AxisAnchor, same_direction: bool = False) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a concentric constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_distance_rassembly
## API Definition
```python
def constrain_distance_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor, distance: float, fallback_axis: AxisLike = 'x') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a point-distance constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_offset_rassembly
## API Definition
```python
def constrain_offset_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor, distance: float, axis: AxisLike = 'z') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add an axial offset constraint and return a new assembly.
@@ -0,0 +1,65 @@
# cut_rsolidlist
## API Definition
```python
def cut_rsolidlist(*solids: Union[Solid, Sequence[Solid]]) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean difference of solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
`cut_rsolidlist(base, [tool_a, tool_b])` is valid input.
If an earlier union returned multiple solids, keep that list and process each
solid intentionally instead of collapsing it to `result[0]` without proof.
If a later step truly requires one solid, first verify `len(results) == 1`.
When a preceding union produced multiple tangent-only solids, adjust the part
placement so the intended bodies overlap slightly, re-run the union, and only
then unwrap the single result.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing; the first solid is the base, the rest are subtracted in order.
## Returns
List[Solid]: A list containing the cut result solid, or an empty list when
there is no valid input. The result is returned as a list for consistency
with other boolean operations.
## Examples
### Example 1
```python
body = make_box_rsolid(12, 4, 4, bottom_face_center=(0, 0, 0))
slot = make_box_rsolid(2, 2, 6, bottom_face_center=(2, 1, -1))
relief = make_cylinder_rsolid(radius=0.8, height=6, center=(8, 2, 2))
```
### Example 2
```python
results = cut_rsolidlist(body, [slot, relief])
print(f"Cut result count: {len(results)}")
```
### Example 3
```python
# If a previous union returned multiple solids, keep the list and cut each part.
tangent_parts = union_rsolidlist(
body,
[
make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0)),
make_sphere_rsolid(2.0, center=(14.0, 2.0, 2.0)),
],
)
trimmed_parts = []
for part in tangent_parts:
trimmed_parts.extend(cut_rsolidlist(part, [slot, relief]))
```
@@ -0,0 +1,35 @@
# eval_rarray
## API Definition
```python
def eval_rarray(field: ScalarField, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray
```
*Source: field.py*
## Description
Evaluate a scalar field on arrays of points.
## Parameters
### field
- **Description**: Scalar field.
### xs
- **Description**: Array of X coordinates.
### ys
- **Description**: Array of Y coordinates.
### zs
- **Description**: Array of Z coordinates.
## Returns
np.ndarray: Array of field values.
@@ -0,0 +1,35 @@
# eval_rscalar
## API Definition
```python
def eval_rscalar(field: ScalarField, x: float, y: float, z: float) -> float
```
*Source: field.py*
## Description
Evaluate a scalar field at a single point.
## Parameters
### field
- **Description**: Scalar field.
### x
- **Description**: X coordinate.
### y
- **Description**: Y coordinate.
### z
- **Description**: Z coordinate.
## Returns
float: Field value.
@@ -0,0 +1,47 @@
# export_step
## API Definition
```python
def export_step(shapes: Union[AnyShape, Sequence[AnyShape]], filename: str) -> None
```
*Source: operations.py*
## Description
Export shapes to STEP.
Use this function when you want to export one shape or many shapes into the
same STEP file. Passing `List[Solid]` is valid and often preferred when a
previous boolean operation returned multiple solids.
## Parameters
### shapes
- **Description**: A single exportable shape or any nested sequence of exportable shapes. Lists of Solid are supported directly, including list results returned by boolean operations.
### filename
- **Description**: Output STEP file path.
## Returns
None: Writes the provided shapes into one STEP file.
## Examples
### Example 1
```python
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
```
### Example 2
```python
# Export the full list directly; no need to collapse to body_parts[0].
export_step(body_parts, "rounded_bar.step")
```
@@ -0,0 +1,47 @@
# export_stl
## API Definition
```python
def export_stl(shapes: Union[AnyShape, Sequence[AnyShape]], filename: str) -> None
```
*Source: operations.py*
## Description
Export shapes to STL.
Use this function when you want to export one solid or many solids/faces into
the same STL file. Passing `List[Solid]` is valid and often preferred when a
previous boolean operation returned multiple solids.
## Parameters
### shapes
- **Description**: A single Solid or Face, or any nested sequence of Solid/Face. Lists of Solid are supported directly, including list results returned by boolean operations.
### filename
- **Description**: Output STL file path.
## Returns
None: Writes the provided shapes into one STL file.
## Examples
### Example 1
```python
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
```
### Example 2
```python
# Export the list result directly.
export_stl(body_parts, "rounded_bar.stl")
```
@@ -0,0 +1,13 @@
# extrude_rsolid
## API Definition
```python
def extrude_rsolid(profile: Union[Wire, Face], direction: Tuple[float, float, float], distance: float) -> Solid
```
*Source: operations.py*
## Description
Create a solid by extruding a profile.
@@ -0,0 +1,13 @@
# fillet_rsolid
## API Definition
```python
def fillet_rsolid(solid: Solid, edges: List[Edge], radius: float) -> Solid
```
*Source: operations.py*
## Description
Apply fillets to selected solid edges.
@@ -0,0 +1,29 @@
# geo
## API Definition
```python
def geo(field: str, default: Any = None) -> KeyFn
```
*Source: ql.py*
## Description
Convenience builder for a `geo` metadata getter.
Q.select(items).order_by(Q.geo("height"))
## Parameters
### field
- **Description**: `geo` field name, such as `type` or `height`.
### default
- **Description**: Default value when lookup fails.
## Returns
Callable[[Any], Any]: Getter function.
@@ -0,0 +1,13 @@
# helical_sweep_rsolid
## API Definition
```python
def helical_sweep_rsolid(profile: Wire, pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a solid by sweeping a profile along a helical path.
@@ -0,0 +1,23 @@
# intersect_rscalarfield
## API Definition
```python
def intersect_rscalarfield(*fields: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create an intersection scalar field.
## Parameters
### *fields
- **Description**: Input scalar fields.
## Returns
ScalarField: Intersection scalar field.
@@ -0,0 +1,65 @@
# intersect_rsolidlist
## API Definition
```python
def intersect_rsolidlist(*solids: Union[Solid, Sequence[Solid]]) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean intersection of solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
`intersect_rsolidlist(body, [clip_a, clip_b])` is valid input.
If an earlier union returned multiple solids, keep that list and intersect
each solid intentionally instead of collapsing it to `result[0]`.
If a later step truly requires one solid, first verify `len(results) == 1`.
When a preceding union produced multiple tangent-only solids, adjust the part
placement so the intended bodies overlap slightly, re-run the union, and only
then unwrap the single result.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing.
## Returns
List[Solid]: A list containing the intersection result, or an empty list if
the solids do not overlap. The result is returned as a list for
consistency with other boolean operations.
## Examples
### Example 1
```python
body = make_box_rsolid(12, 4, 4, bottom_face_center=(0, 0, 0))
clip_a = make_box_rsolid(8, 4, 4, bottom_face_center=(2, 0, 0))
clip_b = make_box_rsolid(6, 6, 6, bottom_face_center=(3, -1, -1))
```
### Example 2
```python
results = intersect_rsolidlist(body, [clip_a, clip_b])
print(f"Intersect result count: {len(results)}")
```
### Example 3
```python
# A previous union may return multiple solids; keep the list and intersect each part.
tangent_parts = union_rsolidlist(
body,
[
make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0)),
make_sphere_rsolid(2.0, center=(14.0, 2.0, 2.0)),
],
)
clipped_parts = []
for part in tangent_parts:
clipped_parts.extend(intersect_rsolidlist(part, clip_a))
```
@@ -0,0 +1,13 @@
# linear_pattern_rsolidlist
## API Definition
```python
def linear_pattern_rsolidlist(shape: AnyShape, direction: Tuple[float, float, float], count: int, spacing: float) -> List[Solid]
```
*Source: operations.py*
## Description
Create a linear pattern of solids.
@@ -0,0 +1,13 @@
# loft_rsolid
## API Definition
```python
def loft_rsolid(profiles: List[Wire], ruled: bool = False) -> Solid
```
*Source: operations.py*
## Description
Create a solid by lofting multiple profiles.
@@ -0,0 +1,13 @@
# make_angle_arc_redge
## API Definition
```python
def make_angle_arc_redge(center: Tuple[float, float, float], radius: float, start_angle: float, end_angle: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create an arc edge from a center, radius, and angle range.
@@ -0,0 +1,13 @@
# make_angle_arc_rwire
## API Definition
```python
def make_angle_arc_rwire(center: Tuple[float, float, float], radius: float, start_angle: float, end_angle: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing an arc defined by a center, radius, and angle range.
@@ -0,0 +1,13 @@
# make_assembly_rassembly
## API Definition
```python
def make_assembly_rassembly(parts: Sequence[Tuple[str, Solid]], name: str = 'assembly', parents: Optional[Dict[str, str]] = None, local_transforms: Optional[Dict[str, Union[np.ndarray, Sequence[Sequence[float]]]]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-1 mapping: lift a parameter description into an assembly object.
@@ -0,0 +1,27 @@
# make_box_rscalarfield
## API Definition
```python
def make_box_rscalarfield(center: Tuple[float, float, float], size: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Create an axis-aligned box scalar field.
## Parameters
### center
- **Description**: Box center coordinates `(x, y, z)`.
### size
- **Description**: Box size `(sx, sy, sz)`.
## Returns
ScalarField: Box scalar field.
@@ -0,0 +1,13 @@
# make_box_rsolid
## API Definition
```python
def make_box_rsolid(width: float, height: float, depth: float, bottom_face_center: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a box solid.
@@ -0,0 +1,31 @@
# make_capsule_rscalarfield
## API Definition
```python
def make_capsule_rscalarfield(p0: Tuple[float, float, float], p1: Tuple[float, float, float], radius: float) -> ScalarField
```
*Source: field.py*
## Description
Create a capsule scalar field.
## Parameters
### p0
- **Description**: First endpoint coordinates.
### p1
- **Description**: Second endpoint coordinates.
### radius
- **Description**: Capsule radius.
## Returns
ScalarField: Capsule scalar field.
@@ -0,0 +1,13 @@
# make_circle_redge
## API Definition
```python
def make_circle_redge(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create a circular edge.
@@ -0,0 +1,13 @@
# make_circle_rface
## API Definition
```python
def make_circle_rface(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a circular face.
@@ -0,0 +1,13 @@
# make_circle_rwire
## API Definition
```python
def make_circle_rwire(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a circular wire.
@@ -0,0 +1,13 @@
# make_cone_rsolid
## API Definition
```python
def make_cone_rsolid(bottom_radius: float, height: float, top_radius: float = 0.0, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a cone or truncated cone solid.
@@ -0,0 +1,13 @@
# make_cylinder_rsolid
## API Definition
```python
def make_cylinder_rsolid(radius: float, height: float, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a cylinder solid.
@@ -0,0 +1,27 @@
# make_ellipsoid_rscalarfield
## API Definition
```python
def make_ellipsoid_rscalarfield(center: Tuple[float, float, float], radii: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Create an ellipsoid scalar field.
## Parameters
### center
- **Description**: Ellipsoid center coordinates `(x, y, z)`.
### radii
- **Description**: Radii `(rx, ry, rz)`.
## Returns
ScalarField: Ellipsoid scalar field.
@@ -0,0 +1,13 @@
# make_face_from_wire_rface
## API Definition
```python
def make_face_from_wire_rface(wire: Wire, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a face from a closed wire.
@@ -0,0 +1,13 @@
# make_field_surface_rsolid
## API Definition
```python
def make_field_surface_rsolid(field, bounds: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None, resolution: Tuple[int, int, int] = (24, 24, 24), iso: float = 0.0, cap_bounds: bool = True) -> Solid
```
*Source: operations.py*
## Description
Build a closed solid from a scalar field isosurface.
@@ -0,0 +1,13 @@
# make_helix_redge
## API Definition
```python
def make_helix_redge(pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create a helix edge.
@@ -0,0 +1,13 @@
# make_helix_rwire
## API Definition
```python
def make_helix_rwire(pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a helix wire.
@@ -0,0 +1,13 @@
# make_line_redge
## API Definition
```python
def make_line_redge(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Create a straight edge between two points.
@@ -0,0 +1,13 @@
# make_n_hole_flange_rsolid
## API Definition
```python
def make_n_hole_flange_rsolid(flange_outer_diameter = 120.0, flange_inner_diameter = 60.0, flange_thickness = 15.0, boss_outer_diameter = 80.0, boss_height = 5.0, hole_diameter = 8.0, hole_circle_diameter = 100.0, hole_count = 8, chamfer_size = 1.0) -> Solid
```
*Source: evolve.py*
## Description
Create an n-hole flange with a raised boss ring and optional chamfers. The center of the bottom face is placed at the origin.
@@ -0,0 +1,13 @@
# make_naca_propeller_blade_rsolid
## API Definition
```python
def make_naca_propeller_blade_rsolid(blade_length = 5.0, root_chord = 1.5, tip_chord = 0.3, total_twist_angle = 45.0, num_sections = 7, t_c = 0.16) -> Solid
```
*Source: evolve.py*
## Description
Create a single propeller blade solid from a twisted NACA 0016 profile. The blade root starts at the origin and extends along +Z.
@@ -0,0 +1,13 @@
# make_point_rvertex
## API Definition
```python
def make_point_rvertex(x: float, y: float, z: float) -> Vertex
```
*Source: operations.py*
## Description
Create a point in 3D space and return it as a vertex.
@@ -0,0 +1,13 @@
# make_polyline_rwire
## API Definition
```python
def make_polyline_rwire(points: List[Tuple[float, float, float]], closed: bool = False) -> Wire
```
*Source: operations.py*
## Description
Create a polyline wire from a point list.
@@ -0,0 +1,13 @@
# make_rectangle_rface
## API Definition
```python
def make_rectangle_rface(width: float, height: float, center: Tuple[float, float, float] = (0, 0, 0), normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a rectangular face.
@@ -0,0 +1,13 @@
# make_rectangle_rwire
## API Definition
```python
def make_rectangle_rwire(width: float, height: float, center: Tuple[float, float, float] = (0, 0, 0), normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a rectangular wire.
@@ -0,0 +1,13 @@
# make_segment_redge
## API Definition
```python
def make_segment_redge(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Alias of `make_line_redge` that returns a straight edge.
@@ -0,0 +1,13 @@
# make_segment_rwire
## API Definition
```python
def make_segment_rwire(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing a single straight segment.
@@ -0,0 +1,27 @@
# make_sphere_rscalarfield
## API Definition
```python
def make_sphere_rscalarfield(center: Tuple[float, float, float], radius: float) -> ScalarField
```
*Source: field.py*
## Description
Create a spherical scalar field.
## Parameters
### center
- **Description**: Sphere center coordinates `(x, y, z)`.
### radius
- **Description**: Sphere radius.
## Returns
ScalarField: Sphere scalar field.
@@ -0,0 +1,13 @@
# make_sphere_rsolid
## API Definition
```python
def make_sphere_rsolid(radius: float, center: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a sphere solid.
@@ -0,0 +1,13 @@
# make_spline_redge
## API Definition
```python
def make_spline_redge(points: List[Tuple[float, float, float]], tangents: Optional[List[Tuple[float, float, float]]] = None) -> Edge
```
*Source: operations.py*
## Description
Create a spline edge through control points.
@@ -0,0 +1,13 @@
# make_spline_rwire
## API Definition
```python
def make_spline_rwire(points: List[Tuple[float, float, float]], tangents: Optional[List[Tuple[float, float, float]]] = None, closed: bool = False) -> Wire
```
*Source: operations.py*
## Description
Create a spline wire through control points.
@@ -0,0 +1,13 @@
# make_threaded_rod_rsolid
## API Definition
```python
def make_threaded_rod_rsolid(thread_diameter = 8.0, thread_length = 20.0, total_length = 30.0, thread_pitch = 1.25, thread_start_position = 0.0, chamfer_size = 0.5) -> Solid
```
*Source: evolve.py*
## Description
Create a threaded rod with configurable rod length, thread span, and pitch. The top center is placed at the origin and the rod extends in -Z.
@@ -0,0 +1,13 @@
# make_three_point_arc_redge
## API Definition
```python
def make_three_point_arc_redge(start: Tuple[float, float, float], middle: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Create an arc edge from three points.
@@ -0,0 +1,13 @@
# make_three_point_arc_rwire
## API Definition
```python
def make_three_point_arc_rwire(start: Tuple[float, float, float], middle: Tuple[float, float, float], end: Tuple[float, float, float]) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing an arc defined by three points.
@@ -0,0 +1,13 @@
# make_wire_from_edges_rwire
## API Definition
```python
def make_wire_from_edges_rwire(edges: List[Edge]) -> Wire
```
*Source: operations.py*
## Description
Create a wire from a list of connected edges.
@@ -0,0 +1,45 @@
# meta
## API Definition
```python
def meta(path: str, op: str, value: Any) -> Predicate
```
*Source: ql.py*
## Description
Build a metadata-based predicate.
Q.meta("geo.type", "==", "box")
## Parameters
### path
- **Description**: Metadata path, for example `geo.type`.
### op
- **Description**: Comparison operator. Supports `==`, `!=`, `>`, `>=`, `<`, and `<=`.
### value
- **Description**: Comparison target value.
## Returns
Callable[[Any], bool]: Predicate function.
## Raises
- **TypeError**: If op is not a string.
- **ValueError**: If the operator is unsupported.
## Examples
```python
pred = Q.meta("geo.size.x", ">", 1.0)
matched = pred(obj)
```
@@ -0,0 +1,13 @@
# mirror_shape
## API Definition
```python
def mirror_shape(shape: AnyShape, plane_origin: Tuple[float, float, float], plane_normal: Tuple[float, float, float]) -> AnyShape
```
*Source: operations.py*
## Description
Mirror a shape across a plane.
@@ -0,0 +1,25 @@
# not_
## API Definition
```python
def not_(predicate: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build a NOT predicate.
Q.not_(Q.tag("state.*"))
## Parameters
### predicate
- **Description**: A single predicate.
## Returns
Callable[[Any], bool]: Negated predicate.
@@ -0,0 +1,25 @@
# or_
## API Definition
```python
def or_(*predicates: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build an OR-composed predicate.
Q.or_(Q.tag("face.top"), Q.tag("face.bottom"))
## Parameters
### *predicates
- **Description**: Any number of predicates.
## Returns
Callable[[Any], bool]: Combined predicate.
@@ -0,0 +1,13 @@
# radial_pattern_rsolidlist
## API Definition
```python
def radial_pattern_rsolidlist(shape: AnyShape, center: Tuple[float, float, float], axis: Tuple[float, float, float], count: int, total_rotation_angle: float) -> List[Solid]
```
*Source: operations.py*
## Description
Create a radial pattern of solids.
@@ -0,0 +1,13 @@
# render_screenshot_rpath
## API Definition
```python
def render_screenshot_rpath(shapes: Union[Solid, Sequence[Solid]], output_path: str, highlight_tags: Optional[Sequence[str]] = None, tag_labels: Optional[Dict[str, str]] = None, image_size: Tuple[int, int] = (1400, 900), view: Union[Tuple[float, float], str] = 'auto', show_axes: bool = True, show_legend: bool = True, zoom: float = 4.0) -> str
```
*Source: operations.py*
## Description
Render a screenshot of shapes and save it to a file.
@@ -0,0 +1,13 @@
# revolve_rsolid
## API Definition
```python
def revolve_rsolid(profile: Union[Wire, Face], axis: Tuple[float, float, float] = (0, 0, 1), angle: float = 360, origin: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a solid by revolving a profile around an axis.
@@ -0,0 +1,13 @@
# rotate_part_rassembly
## API Definition
```python
def rotate_part_rassembly(assembly: Assembly, part: Union[str, PartHandle], angle_deg: float, axis: AxisLike = 'z', origin: Vec3Like = (0.0, 0.0, 0.0), frame: Literal['world', 'local'] = 'world') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: rotate a part and return a new assembly.
@@ -0,0 +1,31 @@
# rotate_rscalarfield
## API Definition
```python
def rotate_rscalarfield(field: ScalarField, axis: Tuple[float, float, float], angle_degrees: float) -> ScalarField
```
*Source: field.py*
## Description
Rotate a scalar field around the origin.
## Parameters
### field
- **Description**: Input scalar field.
### axis
- **Description**: Rotation axis vector `(x, y, z)`.
### angle_degrees
- **Description**: Rotation angle in degrees.
## Returns
ScalarField: Rotated scalar field.
@@ -0,0 +1,13 @@
# rotate_shape
## API Definition
```python
def rotate_shape(shape: AnyShape, angle: float, axis: Tuple[float, float, float] = (0, 0, 1), origin: Tuple[float, float, float] = (0, 0, 0)) -> AnyShape
```
*Source: operations.py*
## Description
Rotate a shape around an axis.
@@ -0,0 +1,27 @@
# scale_rscalarfield
## API Definition
```python
def scale_rscalarfield(field: ScalarField, factors: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Scale a scalar field around the origin.
## Parameters
### field
- **Description**: Input scalar field.
### factors
- **Description**: Scale factors `(sx, sy, sz)`.
## Returns
ScalarField: Scaled scalar field.
@@ -0,0 +1,25 @@
# select
## API Definition
```python
def select(items: Iterable[Any]) -> Query
```
*Source: ql.py*
## Description
Create a query object.
Q.select(items).where(Q.tag("face.top")).first()
## Parameters
### items
- **Description**: Any iterable.
## Returns
Query: Query object.
@@ -0,0 +1,13 @@
# select_edges_by_tag
## API Definition
```python
def select_edges_by_tag(shape: Union[Face, Solid], tag: str) -> List[Edge]
```
*Source: operations.py*
## Description
Select edges by tag.
@@ -0,0 +1,13 @@
# select_faces_by_tag
## API Definition
```python
def select_faces_by_tag(solid: Solid, tag: str) -> List[Face]
```
*Source: operations.py*
## Description
Select faces by tag.
@@ -0,0 +1,13 @@
# set_tag
## API Definition
```python
def set_tag(shape: AnyShape, tag: str) -> AnyShape
```
*Source: operations.py*
## Description
Attach a tag to a shape.
@@ -0,0 +1,13 @@
# shell_rsolid
## API Definition
```python
def shell_rsolid(solid: Solid, faces_to_remove: List[Face], thickness: float) -> Solid
```
*Source: operations.py*
## Description
Shell a solid to create a hollow part.
@@ -0,0 +1,31 @@
# smooth_subtract_rscalarfield
## API Definition
```python
def smooth_subtract_rscalarfield(a: ScalarField, b: ScalarField, k: float) -> ScalarField
```
*Source: field.py*
## Description
Create a smooth subtraction scalar field.
## Parameters
### a
- **Description**: Minuend scalar field.
### b
- **Description**: Subtrahend scalar field.
### k
- **Description**: Smoothing factor, which must be positive.
## Returns
ScalarField: Smooth subtraction scalar field.
@@ -0,0 +1,31 @@
# smooth_union_rscalarfield
## API Definition
```python
def smooth_union_rscalarfield(a: ScalarField, b: ScalarField, k: float) -> ScalarField
```
*Source: field.py*
## Description
Create a smooth union scalar field.
## Parameters
### a
- **Description**: Scalar field A.
### b
- **Description**: Scalar field B.
### k
- **Description**: Smoothing factor, which must be positive.
## Returns
ScalarField: Smooth union scalar field.
@@ -0,0 +1,13 @@
# solve_assembly_rresult
## API Definition
```python
def solve_assembly_rresult(assembly: Assembly, max_iterations: int = 30, tolerance: float = 1e-06) -> AssemblyResult
```
*Source: constraints.py*
## Description
Type-2 mapping: map an assembly to a solve result without mutating it.
@@ -0,0 +1,29 @@
# stack
## API Definition
```python
def stack(assembly: Assembly, parts: Sequence[Union[str, PartHandle]], axis: str = 'z', gap: float = 0.0, align: Literal['center', 'start', 'end'] = 'center', justify: Literal['start', 'center', 'end', 'space-between'] = 'start', bounds: Optional[Tuple[PointAnchor, PointAnchor]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Declaratively stack multiple parts along the specified axis.
Semantics:
- sequential stacking: part i is placed after part i-1 with the given gap
- cross-axis alignment: the other two axes are aligned according to `align`
- main-axis distribution: `justify` controls how the whole stack is placed
within the bounds
BBox-first note:
- This function uses axis-aligned bounding-box (AABB) anchors such as
`bbox.top` and `bbox.bottom` to approximate Flexbox-like box semantics.
- For parts with large rotations, the AABB changes with pose, so the layout
result changes as well. This is expected in the current MVP stage.
Note:
- This is container-level sugar that compiles into a set of `offset(...)`
constraints internally.
@@ -0,0 +1,13 @@
# stack_rassembly
## API Definition
```python
def stack_rassembly(assembly: Assembly, parts: Sequence[Union[str, PartHandle]], axis: str = 'z', gap: float = 0.0, align: Literal['center', 'start', 'end'] = 'center', justify: Literal['start', 'center', 'end', 'space-between'] = 'start', bounds: Optional[Tuple[PointAnchor, PointAnchor]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: apply a stack layout and return a new assembly.
@@ -0,0 +1,27 @@
# subtract_rscalarfield
## API Definition
```python
def subtract_rscalarfield(a: ScalarField, b: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create a subtraction scalar field.
## Parameters
### a
- **Description**: Minuend scalar field.
### b
- **Description**: Subtrahend scalar field.
## Returns
ScalarField: Subtraction scalar field.
@@ -0,0 +1,13 @@
# sweep_rsolid
## API Definition
```python
def sweep_rsolid(profile: Face, path: Wire, is_frenet: bool = False) -> Solid
```
*Source: operations.py*
## Description
Create a solid by sweeping a profile along a path.
@@ -0,0 +1,37 @@
# tag
## API Definition
```python
def tag(pattern: str) -> Predicate
```
*Source: ql.py*
## Description
Build a tag-based predicate.
`Q.tag("face.top")` or `Q.tag("role.*")`.
## Parameters
### pattern
- **Description**: Tag matching pattern. Supports a trailing `*` wildcard.
## Returns
Callable[[Any], bool]: Predicate function.
## Raises
- **TypeError**: If pattern is not a string.
- **ValueError**: If the wildcard position is invalid.
## Examples
```python
pred = Q.tag("role.*")
matched = pred(obj)
```
@@ -0,0 +1,13 @@
# translate_part_rassembly
## API Definition
```python
def translate_part_rassembly(assembly: Assembly, part: Union[str, PartHandle], vector: Vec3Like, frame: Literal['world', 'local'] = 'world') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: translate a part and return a new assembly.
@@ -0,0 +1,27 @@
# translate_rscalarfield
## API Definition
```python
def translate_rscalarfield(field: ScalarField, offset: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Translate a scalar field.
## Parameters
### field
- **Description**: Input scalar field.
### offset
- **Description**: Translation vector `(dx, dy, dz)`.
## Returns
ScalarField: Translated scalar field.
@@ -0,0 +1,13 @@
# translate_shape
## API Definition
```python
def translate_shape(shape: AnyShape, vector: Tuple[float, float, float]) -> AnyShape
```
*Source: operations.py*
## Description
Translate a shape by an offset vector.
@@ -0,0 +1,23 @@
# union_rscalarfield
## API Definition
```python
def union_rscalarfield(*fields: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create a union scalar field.
## Parameters
### *fields
- **Description**: Input scalar fields.
## Returns
ScalarField: Union scalar field.
@@ -0,0 +1,99 @@
# union_rsolidlist
## API Definition
```python
def union_rsolidlist(
*solids: Union[Solid, Sequence[Solid]],
clean: bool = True,
glue: bool = True,
tol: Optional[float] = None,
) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean union of one or more solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
Keep the list result unless you have explicitly verified `len(result) == 1`.
SimpleCAD enables glue mode by default and applies a conservative internal fuzzy
tolerance, so normal modeling code does not need to tune boolean parameters.
By default this follows CadQuery's union flow: perform one OCC fuse across the
input solids, then call `clean()` to unify same-domain faces when possible.
Touching-but-not-intersecting inputs can legitimately return multiple solids.
If that happens, keep using the list: pass it directly into later union calls,
or iterate over the solids for later cut/intersect steps.
When the returned solids are still separated by more than the active tolerance,
SimpleCAD prints a stdout warning explaining that the objects do not touch and
that their gap exceeds `tol`.
If you truly need exactly one merged solid, you must check the list length
before using `result[0]`.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing.
### clean
- **Description**: Call CadQuery's `clean()` after the union to remove splitter edges and unify same-domain faces when possible.
### glue
- **Description**: Enable OCC glue mode for touching or partially overlapping inputs. Defaults to `True` for SimpleCAD's standard union behavior.
### tol
- **Description**: Optional fuzzy-boolean tolerance passed to the OCC union kernel. When omitted, SimpleCAD derives a conservative scale-aware value automatically.
## Returns
List[Solid]: Resulting solids after union attempts. Solids that can be fused
are merged; disjoint or tangent-only contacts remain separate, so the
list may contain multiple solids.
## Examples
### Example 1
```python
# Rounded-bar style input: end caps only touch the center body.
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
```
### Example 2
```python
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
print(f"Union result count: {len(body_parts)}")
# This is acceptable: tangent-only contact can stay as multiple solids.
for solid in body_parts:
print(f"- volume: {solid.get_volume():.6f}")
```
### Example 3
```python
# Keep using the returned list in later boolean steps.
rib = make_box_rsolid(2, 4, 4, bottom_face_center=(4, 0, 0))
combined_parts = union_rsolidlist(body_parts, rib)
print(f"Combined result count: {len(combined_parts)}")
```
### Example 4
```python
# Only unwrap to one solid after an explicit length check.
left_cap_embedded = make_sphere_rsolid(2.0, center=(-1.8, 2.0, 2.0))
right_cap_embedded = make_sphere_rsolid(2.0, center=(11.8, 2.0, 2.0))
merged = union_rsolidlist(main_body, [left_cap_embedded, right_cap_embedded])
if len(merged) != 1:
raise ValueError(
"Adjust part placement so each cap overlaps the body slightly before "
"using merged[0]."
)
final_body = merged[0]
```
@@ -0,0 +1,36 @@
# value
## API Definition
```python
def value(path: str, default: Any = None) -> KeyFn
```
*Source: ql.py*
## Description
Build a value getter for sorting or projection.
Q.select(items).order_by(Q.value("geo.height"))
## Parameters
### path
- **Description**: Metadata path, for example `geo.height`.
### default
- **Description**: Default value when lookup fails.
## Returns
Callable[[Any], Any]: Getter function.
## Examples
```python
key = Q.value("geo.height", 0.0)
height = key(obj)
```
@@ -0,0 +1,213 @@
# SimpleCAD API Core Classes Documentation
This directory contains detailed documentation for all core classes of SimpleCAD API.
## Core Classes Overview
SimpleCAD API provides a complete set of geometric modeling classes, from basic points, lines, and surfaces to complex solids and compounds. Each class has rich functionality and a flexible tag management system.
### Basic Classes
#### [CoordinateSystem - Coordinate System](coordinate_system.md)
A 3D coordinate system class for defining and managing local coordinate systems, supporting coordinate transformations and integration with CADQuery.
**Main Features:**
- Define local coordinate systems
- Coordinate and vector transformations
- Conversion with CADQuery coordinate systems
#### [SimpleWorkplane - Workplane](simple_workplane.md)
A workplane context manager that provides a local coordinate system environment, supporting nested usage.
**Main Features:**
- Define workplanes
- Context manager support
- Nested coordinate system management
#### [TaggedMixin - Tag Mixin Class](tagged_mixin.md)
A mixin class that provides unified tag and metadata management functionality for all geometry classes.
**Main Features:**
- Tag management (add, remove, query)
- Metadata storage and retrieval
- Geometry classification and query support
### Geometry Classes
#### 0D Geometry
##### [Vertex - Vertex](vertex.md)
Represents a point in 3D space, the fundamental element of all geometries.
**Main Features:**
- Store 3D coordinates
- Tag and metadata management
- Coordinate queries
#### 1D Geometry
##### [Edge - Edge](edge.md)
Represents a 1D geometric element connecting two vertices, which can be lines, arcs, splines, etc.
**Main Features:**
- Length calculation
- Endpoint queries
- Geometry type identification
##### [Wire - Wire](wire.md)
A 1D geometric path composed of multiple connected edges, which can be open or closed.
**Main Features:**
- Edge collection management
- Closure checking
- Path analysis
#### 2D Geometry
##### [Face - Face](face.md)
Represents 2D surface geometry, enclosed by one or more wires, and can contain holes.
**Main Features:**
- Area calculation
- Normal vector queries
- Boundary wire management
##### [Shell - Shell](shell.md)
A surface collection composed of multiple faces, which can be open or closed.
**Main Features:**
- Face collection management
- Surface analysis
- Thin-walled structure support
#### 3D Geometry
##### [Solid - Solid](solid.md)
Represents a 3D closed geometry with volume, the core object of CAD modeling.
**Main Features:**
- Volume calculation
- Face and edge queries
- Automatic face tagging
- Boolean operation support
##### [Compound - Compound](compound.md)
A collection composed of multiple geometry objects, used for managing complex assemblies.
**Main Features:**
- Multi-solid management
- Hierarchy support
- Batch operations
## Class Relationship Diagram
```
TaggedMixin
├── Vertex (0D)
├── Edge (1D)
├── Wire (1D) ← composed of Edges
├── Face (2D) ← bounded by Wires
├── Shell (2D) ← collection of Faces
├── Solid (3D) ← bounded by Shells/Faces
└── Compound (3D) ← collection of Solids
CoordinateSystem ← independent utility class
SimpleWorkplane ← uses CoordinateSystem
```
## Inheritance Relationships
All geometry classes inherit from `TaggedMixin`, obtaining unified tag and metadata management functionality:
- **Tag System**: Add string tags to geometries, supporting classification and queries
- **Metadata System**: Store key-value pairs, supporting complex attribute management
- **Query Support**: Efficient querying and filtering based on tags and metadata
## Coordinate System
SimpleCAD uses a unified coordinate system:
- **Global Coordinate System**: Z-up right-handed coordinate system
- **Local Coordinate Systems**: Defined through `CoordinateSystem` and `SimpleWorkplane`
- **CADQuery Compatible**: Automatically handles conversion with CADQuery coordinate systems
## Design Principles
### Consistency
All classes follow the same design patterns and naming conventions, providing a consistent user experience.
### Extensibility
Through the tag and metadata system, users can add custom information to geometries, supporting complex application scenarios.
### Interoperability
Seamlessly integrates with CADQuery, fully utilizing CADQuery's powerful functionality.
### Ease of Use
Provides intuitive APIs and rich examples, reducing learning costs.
## Usage Guide
### Basic Usage Flow
1. **Create Geometries**: Use `make_*` functions to create basic geometries
2. **Add Tags**: Use `add_tag()` to add identifiers to geometries
3. **Set Metadata**: Use `set_metadata()` to store attribute information
4. **Combine Operations**: Use boolean operations, transformations, etc. to create complex geometries
5. **Query and Filter**: Query desired geometries based on tags and metadata
### Best Practices
1. **Tag Naming**: Use consistent naming conventions, such as `category.subcategory.detail`
2. **Metadata Organization**: Use structured data to organize related information
3. **Coordinate System Management**: Use workplanes appropriately to simplify complex geometry creation
4. **Performance Considerations**: Avoid excessive tags and large metadata that may impact performance
## Example Code
```python
from simplecadapi import *
# 创建工作平面
with SimpleWorkplane(origin=(0, 0, 0)) as wp:
# 创建基础几何体
box = make_box_rsolid(width=5, height=3, depth=2)
# 添加标签和元数据
box.add_tag("structural")
box.add_tag("aluminum")
box.set_metadata("material", "6061-T6")
box.set_metadata("density", 2.7)
# 自动标记面
box.auto_tag_faces("box")
# 查询特定面
top_faces = [f for f in box.get_faces() if f.has_tag("top")]
```
## Extension Development
If you need to create custom geometry classes:
1. Inherit from `TaggedMixin` to get tag functionality
2. Wrap the corresponding CADQuery object
3. Implement necessary geometry query methods
4. Provide appropriate string representation methods
```python
class CustomGeometry(TaggedMixin):
def __init__(self, cq_object):
TaggedMixin.__init__(self)
self.cq_object = cq_object
def get_custom_property(self):
# 实现自定义功能
pass
```
## More Resources
- [API Reference Documentation](../api/)
- [Example Code](../../examples.py)
- [User Guide](../../README.md)
- [Declarative Constraint Layout Design Draft](declarative_constraints.md)
@@ -0,0 +1,635 @@
# Compound
## Overview
`Compound` is the compound class in the SimpleCAD API, representing a collection of multiple geometry objects. A compound can contain multiple solids, faces, edges, and other types of geometric objects, making it an important tool for handling complex assemblies and multi-body geometry. It wraps the CADQuery Compound object and adds tagging functionality.
## Class Definition
```python
class Compound(TaggedMixin):
"""复合体类,包装CADQuery的Compound,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Manage collections of multiple geometry objects
- Create complex assembly structures
- Batch process multiple geometry objects
- Organize and classify geometry objects
- Implement hierarchical geometry structures
## Constructor
### `__init__(cq_compound)`
Initializes a compound object.
**Parameters:**
- `cq_compound` (cadquery.Compound): A CADQuery compound object
**Raises:**
- `ValueError`: When the input compound object is invalid
**Example:**
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid,
union_rsolidlist
)
# 创建多个实体
box = make_box_rsolid(width=2, height=2, depth=2)
cylinder = make_cylinder_rsolid(center=(3, 0, 0), radius=1, height=2)
sphere = make_sphere_rsolid(center=(0, 3, 0), radius=1)
# 通过布尔运算可能产生复合体
# 注意:实际的复合体创建方式可能因 API 实现而异
```
## Main Properties
- `cq_compound`: The underlying CADQuery compound object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_solids()`
Get all solids that make up the compound.
**Returns:**
- `List[Solid]`: List of solid objects
**Raises:**
- `ValueError`: When solid list retrieval fails
**Example:**
```python
# 假设有一个复合体对象
# compound = ... 某个复合体
solids = compound.get_solids()
print(f"复合体包含 {len(solids)} 个实体")
for i, solid in enumerate(solids):
volume = solid.get_volume()
print(f"实体 {i}: 体积 {volume:.3f}")
```
### Tag Management Methods
Methods inherited from `TaggedMixin`:
#### `add_tag(tag)`, `has_tag(tag)`, `get_tags()`, `remove_tag(tag)`
#### `set_metadata(key, value)`, `get_metadata(key, default=None)`
Usage is similar to Vertex; see [Vertex documentation](vertex.md) for details.
## Usage Examples
### Creating and Managing Compounds
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid,
translate_shape,
rotate_shape
)
def create_compound_assembly():
"""创建复合体装配"""
# 创建基础零件
parts = []
# 主体零件
main_body = make_box_rsolid(width=8, height=6, depth=4)
main_body.add_tag("main_body")
main_body.add_tag("structural")
main_body.set_metadata("part_id", "MB001")
main_body.set_metadata("material", "steel")
parts.append(main_body)
# 圆柱形零件
for i in range(3):
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=2)
cylinder = translate_shape(cylinder, offset=(2 + i*2, 1, 4))
cylinder.add_tag(f"cylinder_{i}")
cylinder.add_tag("fastener")
cylinder.set_metadata("part_id", f"CY{i:03d}")
cylinder.set_metadata("material", "brass")
parts.append(cylinder)
# 球形零件
for i in range(2):
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=0.8)
sphere = translate_shape(sphere, offset=(1 + i*6, 5, 2))
sphere.add_tag(f"sphere_{i}")
sphere.add_tag("decorative")
sphere.set_metadata("part_id", f"SP{i:03d}")
sphere.set_metadata("material", "aluminum")
parts.append(sphere)
# 分析装配体
print(f"装配体分析:")
print(f" 零件总数: {len(parts)}")
# 按类型分类
structural_parts = [p for p in parts if p.has_tag("structural")]
fastener_parts = [p for p in parts if p.has_tag("fastener")]
decorative_parts = [p for p in parts if p.has_tag("decorative")]
print(f" 结构件: {len(structural_parts)}")
print(f" 紧固件: {len(fastener_parts)}")
print(f" 装饰件: {len(decorative_parts)}")
# 按材料分类
materials = {}
for part in parts:
material = part.get_metadata("material", "unknown")
if material not in materials:
materials[material] = []
materials[material].append(part)
print(f" 材料分布:")
for material, part_list in materials.items():
total_volume = sum(p.get_volume() for p in part_list)
print(f" {material}: {len(part_list)} 件, 总体积: {total_volume:.3f}")
return parts
assembly_parts = create_compound_assembly()
```
### Hierarchical Compound Structure
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, translate_shape
def create_hierarchical_compound():
"""创建层次化复合体"""
# 创建子装配1:螺栓组件
bolt_assembly = []
# 螺栓主体
bolt_body = make_cylinder_rsolid(center=(0, 0, 0), radius=0.3, height=3)
bolt_body.add_tag("bolt_body")
bolt_body.add_tag("threaded")
bolt_body.set_metadata("assembly", "bolt_assembly")
bolt_body.set_metadata("function", "fastening")
bolt_assembly.append(bolt_body)
# 螺栓头
bolt_head = make_cylinder_rsolid(center=(0, 0, 3), radius=0.5, height=0.5)
bolt_head.add_tag("bolt_head")
bolt_head.add_tag("hex_head")
bolt_head.set_metadata("assembly", "bolt_assembly")
bolt_head.set_metadata("function", "driving")
bolt_assembly.append(bolt_head)
# 创建子装配2:支架组件
bracket_assembly = []
# 支架主体
bracket_main = make_box_rsolid(width=4, height=1, depth=2)
bracket_main.add_tag("bracket_main")
bracket_main.add_tag("mounting")
bracket_main.set_metadata("assembly", "bracket_assembly")
bracket_main.set_metadata("function", "support")
bracket_assembly.append(bracket_main)
# 支架臂
for i in range(2):
arm = make_box_rsolid(width=0.5, height=2, depth=2)
arm = translate_shape(arm, offset=(0.75 + i*2.5, 1, 0))
arm.add_tag(f"bracket_arm_{i}")
arm.add_tag("support_arm")
arm.set_metadata("assembly", "bracket_assembly")
arm.set_metadata("function", "support")
bracket_assembly.append(arm)
# 创建主装配
main_assembly = []
# 添加子装配
main_assembly.extend(bolt_assembly)
main_assembly.extend(bracket_assembly)
# 添加主体零件
main_body = make_box_rsolid(width=8, height=6, depth=4)
main_body.add_tag("main_body")
main_body.add_tag("primary_structure")
main_body.set_metadata("assembly", "main_assembly")
main_body.set_metadata("function", "housing")
main_assembly.append(main_body)
# 分析层次结构
print(f"层次化装配体分析:")
# 按装配分组
assemblies = {}
for part in main_assembly:
assembly_name = part.get_metadata("assembly", "unknown")
if assembly_name not in assemblies:
assemblies[assembly_name] = []
assemblies[assembly_name].append(part)
for assembly_name, parts in assemblies.items():
print(f" {assembly_name}:")
print(f" 零件数: {len(parts)}")
# 按功能分类
functions = {}
for part in parts:
function = part.get_metadata("function", "unknown")
if function not in functions:
functions[function] = []
functions[function].append(part)
for function, func_parts in functions.items():
total_volume = sum(p.get_volume() for p in func_parts)
print(f" {function}: {len(func_parts)} 件, 体积: {total_volume:.3f}")
return main_assembly, assemblies
main_assembly, assemblies = create_hierarchical_compound()
```
### Batch Operations on Compounds
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
translate_shape,
rotate_shape
)
def batch_operations_on_compound():
"""对复合体进行批量操作"""
# 创建一系列相似零件
parts = []
# 创建网格排列的零件
for i in range(3):
for j in range(3):
# 基础几何体
if (i + j) % 2 == 0:
part = make_box_rsolid(width=1, height=1, depth=1)
part.add_tag("box_part")
part.add_tag("cubic")
else:
part = make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=1)
part.add_tag("cylinder_part")
part.add_tag("circular")
# 定位
part = translate_shape(part, offset=(i*2, j*2, 0))
# 添加位置信息
part.add_tag(f"pos_{i}_{j}")
part.add_tag("grid_item")
part.set_metadata("grid_position", (i, j))
part.set_metadata("grid_index", i*3 + j)
parts.append(part)
# 批量分析
print(f"批量操作分析:")
print(f" 总零件数: {len(parts)}")
# 按类型统计
box_parts = [p for p in parts if p.has_tag("box_part")]
cylinder_parts = [p for p in parts if p.has_tag("cylinder_part")]
print(f" 盒子零件: {len(box_parts)}")
print(f" 圆柱零件: {len(cylinder_parts)}")
# 批量体积计算
total_volume = sum(p.get_volume() for p in parts)
box_volume = sum(p.get_volume() for p in box_parts)
cylinder_volume = sum(p.get_volume() for p in cylinder_parts)
print(f" 总体积: {total_volume:.3f}")
print(f" 盒子体积: {box_volume:.3f}")
print(f" 圆柱体积: {cylinder_volume:.3f}")
# 批量属性设置
for part in parts:
volume = part.get_volume()
grid_pos = part.get_metadata("grid_position")
# 根据体积分类
if volume < 0.5:
part.add_tag("small_part")
elif volume < 1.5:
part.add_tag("medium_part")
else:
part.add_tag("large_part")
# 根据位置分类
if grid_pos[0] == 0:
part.add_tag("left_column")
elif grid_pos[0] == 2:
part.add_tag("right_column")
else:
part.add_tag("center_column")
if grid_pos[1] == 0:
part.add_tag("bottom_row")
elif grid_pos[1] == 2:
part.add_tag("top_row")
else:
part.add_tag("center_row")
# 设置材料属性
if part.has_tag("box_part"):
part.set_metadata("material", "aluminum")
part.set_metadata("density", 2.7)
else:
part.set_metadata("material", "steel")
part.set_metadata("density", 7.8)
# 计算质量
density = part.get_metadata("density")
mass = volume * density
part.set_metadata("mass", mass)
# 批量质量分析
total_mass = sum(p.get_metadata("mass") for p in parts)
aluminum_mass = sum(p.get_metadata("mass") for p in parts if p.get_metadata("material") == "aluminum")
steel_mass = sum(p.get_metadata("mass") for p in parts if p.get_metadata("material") == "steel")
print(f" 总质量: {total_mass:.3f}")
print(f" 铝质量: {aluminum_mass:.3f}")
print(f" 钢质量: {steel_mass:.3f}")
# 位置统计
position_stats = {}
for part in parts:
pos = part.get_metadata("grid_position")
if pos not in position_stats:
position_stats[pos] = {"count": 0, "volume": 0, "mass": 0}
position_stats[pos]["count"] += 1
position_stats[pos]["volume"] += part.get_volume()
position_stats[pos]["mass"] += part.get_metadata("mass")
print(f" 位置统计:")
for pos, stats in position_stats.items():
print(f" 位置 {pos}: {stats['count']} 件, 体积: {stats['volume']:.3f}, 质量: {stats['mass']:.3f}")
return parts
batch_parts = batch_operations_on_compound()
```
### Query and Filter Compound
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
def query_and_filter_compound():
"""查询和筛选复合体"""
# 创建多样化的零件集合
parts = []
# 创建不同类型的零件
geometries = [
("small_box", make_box_rsolid(width=1, height=1, depth=1)),
("large_box", make_box_rsolid(width=3, height=2, depth=2)),
("thin_cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=4)),
("wide_cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=1)),
("small_sphere", make_sphere_rsolid(center=(0, 0, 0), radius=0.8)),
("large_sphere", make_sphere_rsolid(center=(0, 0, 0), radius=1.5))
]
# 为每个零件添加详细信息
for name, part in geometries:
part.add_tag(name)
# 几何类型标签
if "box" in name:
part.add_tag("rectangular")
part.add_tag("prismatic")
elif "cylinder" in name:
part.add_tag("cylindrical")
part.add_tag("rotational")
elif "sphere" in name:
part.add_tag("spherical")
part.add_tag("rotational")
# 尺寸标签
volume = part.get_volume()
if volume < 2:
part.add_tag("small")
elif volume < 10:
part.add_tag("medium")
else:
part.add_tag("large")
# 应用标签
if "thin" in name:
part.add_tag("structural")
part.set_metadata("application", "support")
elif "wide" in name:
part.add_tag("base")
part.set_metadata("application", "foundation")
else:
part.add_tag("general")
part.set_metadata("application", "multipurpose")
# 材料属性
if part.has_tag("small"):
part.set_metadata("material", "aluminum")
part.set_metadata("cost_per_unit", 2.5)
elif part.has_tag("medium"):
part.set_metadata("material", "steel")
part.set_metadata("cost_per_unit", 1.8)
else:
part.set_metadata("material", "cast_iron")
part.set_metadata("cost_per_unit", 3.2)
part.set_metadata("volume", volume)
part.set_metadata("name", name)
parts.append(part)
# 查询和筛选示例
print(f"复合体查询和筛选:")
print(f" 总零件数: {len(parts)}")
# 1. 按标签查询
print(f"\n1. 按标签查询:")
small_parts = [p for p in parts if p.has_tag("small")]
cylindrical_parts = [p for p in parts if p.has_tag("cylindrical")]
structural_parts = [p for p in parts if p.has_tag("structural")]
print(f" 小型零件: {len(small_parts)}")
print(f" 圆柱形零件: {len(cylindrical_parts)}")
print(f" 结构零件: {len(structural_parts)}")
# 2. 按体积范围查询
print(f"\n2. 按体积范围查询:")
volume_ranges = [
("超小", 0, 1),
("小", 1, 5),
("中", 5, 15),
("大", 15, float('inf'))
]
for range_name, min_vol, max_vol in volume_ranges:
range_parts = [p for p in parts if min_vol <= p.get_volume() < max_vol]
if range_parts:
print(f" {range_name}体积 ({min_vol}-{max_vol}): {len(range_parts)} 件")
# 3. 按材料查询
print(f"\n3. 按材料查询:")
materials = set(p.get_metadata("material") for p in parts)
for material in materials:
material_parts = [p for p in parts if p.get_metadata("material") == material]
total_volume = sum(p.get_volume() for p in material_parts)
total_cost = sum(p.get_metadata("cost_per_unit", 0) for p in material_parts)
print(f" {material}: {len(material_parts)} 件, 总体积: {total_volume:.3f}, 总成本: {total_cost:.2f}")
# 4. 复合查询
print(f"\n4. 复合查询:")
# 查询小型圆柱形零件
small_cylinders = [p for p in parts if p.has_tag("small") and p.has_tag("cylindrical")]
print(f" 小型圆柱形零件: {len(small_cylinders)}")
# 查询铝制零件
aluminum_parts = [p for p in parts if p.get_metadata("material") == "aluminum"]
print(f" 铝制零件: {len(aluminum_parts)}")
# 查询高成本零件
high_cost_parts = [p for p in parts if p.get_metadata("cost_per_unit", 0) > 3.0]
print(f" 高成本零件: {len(high_cost_parts)}")
# 5. 统计分析
print(f"\n5. 统计分析:")
# 按几何类型统计
geometric_types = ["rectangular", "cylindrical", "spherical"]
for geo_type in geometric_types:
type_parts = [p for p in parts if p.has_tag(geo_type)]
if type_parts:
avg_volume = sum(p.get_volume() for p in type_parts) / len(type_parts)
print(f" {geo_type}: {len(type_parts)} 件, 平均体积: {avg_volume:.3f}")
# 成本效率分析
print(f"\n6. 成本效率分析:")
for part in parts:
volume = part.get_volume()
cost = part.get_metadata("cost_per_unit", 0)
if cost > 0:
efficiency = volume / cost
part.set_metadata("volume_cost_efficiency", efficiency)
# 按效率排序
sorted_parts = sorted(parts, key=lambda p: p.get_metadata("volume_cost_efficiency", 0), reverse=True)
print(f" 最高效率零件: {sorted_parts[0].get_metadata('name')}, 效率: {sorted_parts[0].get_metadata('volume_cost_efficiency'):.3f}")
print(f" 最低效率零件: {sorted_parts[-1].get_metadata('name')}, 效率: {sorted_parts[-1].get_metadata('volume_cost_efficiency'):.3f}")
return parts
filtered_parts = query_and_filter_compound()
```
## String Representation
```python
# 假设有一个复合体对象
# compound = ... 某个复合体
compound.add_tag("assembly")
compound.set_metadata("part_count", 5)
compound.set_metadata("total_volume", 150.0)
print(compound)
```
Output:
```
Compound:
solid_count: 5
solids:
solid_0:
volume: 30.000
face_count: 6
edge_count: 12
solid_1:
volume: 25.000
face_count: 8
edge_count: 16
solid_2:
volume: 40.000
face_count: 6
edge_count: 12
solid_3:
volume: 35.000
face_count: 10
edge_count: 20
solid_4:
volume: 20.000
face_count: 4
edge_count: 8
tags: [assembly]
metadata:
part_count: 5
total_volume: 150.0
```
## Relationships with Other Geometry
- **Solid (Solid)**: Primary components of a compound
- **Face (Face)**: Indirectly associated through solids
- **Edge (Edge)**: Indirectly associated through solids and faces
## Application Scenarios
- **Assembly modeling**: Complex mechanical assemblies
- **Architectural design**: Building group modeling
- **Product design**: Multi-component products
- **Manufacturing planning**: Batch production management
- **Simulation analysis**: Multi-body system analysis
## Management Strategies
### Hierarchical Management
- Use tags and metadata to establish hierarchical structures
- Classify by function, material, process, etc.
- Enable fast querying and batch operations
### Performance Optimization
- Organize compound structures reasonably
- Avoid excessively deep nesting levels
- Optimize query and filtering algorithms
### Data Consistency
- Ensure metadata accuracy
- Maintain relationships between geometry objects
- Update statistics in a timely manner
## Notes
- Compounds may contain many geometry objects; be mindful of performance
- Modifications to geometry objects do not automatically update compound statistics
- Tag and metadata management requires establishing unified naming conventions
- Complex hierarchical structures may lead to decreased query efficiency
- Reasonable data structure design is needed to support efficient batch operations
- When performing geometric operations, consider the interrelationships between objects in the compound
@@ -0,0 +1,175 @@
# CoordinateSystem
## Overview
`CoordinateSystem` is the 3D coordinate system class in SimpleCAD API, used for defining and managing coordinate systems in 3D space. SimpleCAD uses a Z-up right-handed coordinate system with origin at (0, 0, 0), X-axis forward, Y-axis right, and Z-axis up.
## Class Definition
```python
class CoordinateSystem:
"""三维坐标系
SimpleCAD使用Z向上的右手坐标系,原点在(0, 0, 0),X轴向前,Y轴向右,Z轴向上
"""
```
## Usage
- Define local coordinate systems
- Coordinate transformation (local to global coordinates)
- Conversion with CADQuery coordinate systems
- Foundation for geometric transformations
## Constructor
### `__init__(origin, x_axis, y_axis)`
Initialize a coordinate system.
**Parameters:**
- `origin` (Tuple[float, float, float], optional): Coordinate system origin, default (0, 0, 0)
- `x_axis` (Tuple[float, float, float], optional): X-axis direction vector, default (1, 0, 0)
- `y_axis` (Tuple[float, float, float], optional): Y-axis direction vector, default (0, 1, 0)
**Exceptions:**
- `ValueError`: Raised when input coordinates or direction vectors are invalid
**Example:**
```python
from simplecadapi import CoordinateSystem
# 默认坐标系(世界坐标系)
world_cs = CoordinateSystem()
# 自定义坐标系
custom_cs = CoordinateSystem(
origin=(1, 2, 3),
x_axis=(1, 0, 0),
y_axis=(0, 1, 0)
)
# 旋转的坐标系
rotated_cs = CoordinateSystem(
origin=(0, 0, 0),
x_axis=(0.707, 0.707, 0), # 绕Z轴旋转45度
y_axis=(-0.707, 0.707, 0)
)
```
## Main Properties
- `origin`: Coordinate system origin (numpy.ndarray)
- `x_axis`: X-axis direction vector (numpy.ndarray)
- `y_axis`: Y-axis direction vector (numpy.ndarray)
- `z_axis`: Z-axis direction vector (numpy.ndarray, automatically calculated)
## Common Methods
### `transform_point(point)`
Transform local coordinates to global coordinates.
**Parameters:**
- `point` (numpy.ndarray): Local coordinate point
**Returns:**
- `numpy.ndarray`: Global coordinate point
**Example:**
```python
import numpy as np
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(1, 0, 0))
local_point = np.array([1, 0, 0])
global_point = cs.transform_point(local_point)
print(global_point) # [2. 0. 0.]
```
### `transform_vector(vector)`
Transform local direction vectors to global direction vectors (excluding translation).
**Parameters:**
- `vector` (numpy.ndarray): Local direction vector
**Returns:**
- `numpy.ndarray`: Global direction vector
**Example:**
```python
import numpy as np
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(
origin=(0, 0, 0),
x_axis=(0, 1, 0), # X轴指向Y方向
y_axis=(1, 0, 0) # Y轴指向X方向
)
local_vector = np.array([1, 0, 0]) # 局部X方向
global_vector = cs.transform_vector(local_vector)
print(global_vector) # [0. 1. 0.] (全局Y方向)
```
### `to_cq_plane()`
Convert to CADQuery's Plane object.
**Returns:**
- `cadquery.Plane`: CADQuery plane object
**Example:**
```python
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(0, 0, 1))
cq_plane = cs.to_cq_plane()
```
## Coordinate System Transformation
SimpleCAD uses Z-up coordinate system, while CADQuery uses Y-up coordinate system. The conversion rules are:
- SimpleCAD's X-axis (forward) → CADQuery's Z-axis (forward)
- SimpleCAD's Y-axis (right) → CADQuery's X-axis (right)
- SimpleCAD's Z-axis (up) → CADQuery's Y-axis (up)
## Global Coordinate System
SimpleCAD provides a global world coordinate system:
```python
from simplecadapi import WORLD_CS
print(WORLD_CS.origin) # [0. 0. 0.]
print(WORLD_CS.x_axis) # [1. 0. 0.]
print(WORLD_CS.y_axis) # [0. 1. 0.]
print(WORLD_CS.z_axis) # [0. 0. 1.]
```
## String Representation
```python
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(1, 2, 3))
print(cs)
```
Output:
```
CoordinateSystem:
origin: [1.000, 2.000, 3.000]
x_axis: [1.000, 0.000, 0.000]
y_axis: [0.000, 1.000, 0.000]
z_axis: [0.000, 0.000, 1.000]
```
## Notes
- Input direction vectors are automatically normalized
- Z-axis is automatically calculated via the cross product of X-axis and Y-axis
- If a zero vector is input, a ValueError will be raised
- Coordinate systems should maintain right-handed characteristics
@@ -0,0 +1,306 @@
# Declarative Constraints Layout Design Draft
## Background and Goals
The current SimpleCADAPI is primarily imperative modeling: developers need to manually provide specific coordinates, rotation angles, and offsets. For assemblies, this approach is costly in the following scenarios:
1. Geometric relationships are stable but dimensions change frequently (repeated position recalculation after parameter changes).
2. Dependencies between multiple parts are complex (one part change cascades to affect multiple parts).
3. Need to express "relationships" rather than "values" (e.g., coaxial, fit, equidistant distribution).
Web layout (such as HTML/CSS Flexbox) has proven an effective direction:
- Users declare constraints (alignment, distribution, spacing).
- Solvers propagate constraints in the layout tree and compute final geometric values.
This proposal aims to migrate this approach to the CAD SDK:
- Retain existing imperative APIs;
- Add an optional declarative assembly layer;
- Let users describe assembly relationships, with the SDK computing each part's final pose.
## Current Implementation Status (feat/declarative-constraints-layout)
The current branch provides a runnable MVP with the following core capabilities:
- New module: `simplecadapi.constraints`
- New objects: `Assembly`, `PartHandle`, `PointAnchor`, `AxisAnchor`, `AssemblyResult`
- Supports mixed paradigm:
- First imperative pre-positioning (`translate_part` / `rotate_part`)
- Then declarative constraint solving (`coincident` / `concentric` / `offset` / `distance`)
- Supports 1D container syntax sugar: `stack(...)`
- `stack(...)` adds main axis distribution parameter: `justify=start|center|end|space-between`
- When `justify=center/end/space-between` is needed, use `bounds=(start_anchor, end_anchor)` to specify the container's main axis boundary.
Current layout implementation uses a **BBox-first** strategy:
- Alignment and distribution are primarily based on `bbox.*` anchors (AABB).
- This is consistent with Flexbox's box model thinking, but is approximate in 3D:
When parts rotate, AABB changes and layout results change accordingly.
- OBB/feature face anchors can be added later to reduce approximation errors from rotation.
- Supports assembly tree parent-child relationships and local/world transform propagation.
Framework-style constraints (functional) have been explicitly categorized into two types of mappings:
1. **Type-1 lifting mappings** (parameter space -> CAD object space)
- E.g.: `make_*_rsolid`, `make_assembly_rassembly`
2. **Type-2 algebraic transform mappings** (CAD object space -> CAD object space/result space)
- E.g.: `translate_part_rassembly`, `constrain_offset_rassembly`, `stack_rassembly`
- Solving uses `solve_assembly_rresult`, ensuring input assembly objects are not modified
Corresponding functional APIs (do not modify input) include:
- `make_assembly_rassembly`
- `clone_assembly_rassembly`
- `add_part_rassembly`
- `translate_part_rassembly` / `rotate_part_rassembly`
- `constrain_coincident_rassembly` / `constrain_concentric_rassembly`
- `constrain_offset_rassembly` / `constrain_distance_rassembly`
- `stack_rassembly`
- `solve_assembly_rresult`
Functional pipeline example:
```python
import simplecadapi as scad
asm0 = scad.make_assembly_rassembly([
("sleeve", sleeve_solid),
("rod", rod_solid),
])
asm1 = scad.translate_part_rassembly(asm0, "rod", (3.0, -2.0, 4.0))
asm2 = scad.constrain_concentric_rassembly(
asm1,
asm1.part("sleeve").axis("z"),
asm1.part("rod").axis("z"),
)
asm3 = scad.constrain_offset_rassembly(
asm2,
asm2.part("sleeve").anchor("bbox.bottom"),
asm2.part("rod").anchor("bbox.bottom"),
3.0,
axis="z",
)
result = scad.solve_assembly_rresult(asm3)
```
Example (mixed usage):
```python
import simplecadapi as scad
asm = scad.Assembly("demo")
sleeve = asm.add_part("sleeve", sleeve_solid)
rod = asm.add_part("rod", rod_solid)
# 命令式预定位
asm.translate_part("rod", (3.0, -2.0, 4.0), frame="world")
# 声明式约束
asm.concentric(sleeve.axis("z"), rod.axis("z"))
asm.offset(sleeve.anchor("bbox.bottom"), rod.anchor("bbox.bottom"), 3.0, axis="z")
result = asm.solve()
scad.export_step(result.solids(), "assembly.step")
```
## Scope
### In Scope (Phase 1)
- Assembly pose solving (rigid body 6DOF, no part topology modification).
- Basic constraint types:
- `coincident` (point/plane/axis coincidence)
- `concentric` (coaxial)
- `parallel` / `perpendicular` (directional relationships)
- `distance` (spacing)
- `offset` (offset along normal or axis direction)
- "Flex-like" 1D layout containers:
- `stack(axis="x|y|z")`
- `gap`
- `justify` (start/center/end/space-between)
- `align` (start/center/end/stretch*)
`stretch` in CAD does not perform geometric stretching; it only means aligning to the alignment baseline.
### Out of Scope (Phase 1)
- Parameter-driven topology rebuilding (e.g., automatic hole diameter changes, chamfer regeneration).
- General nonlinear symbolic solvers (CAS level).
- Complete sketch constraint system (2D sketch solver).
## Core Abstractions
### 1) Assembly Node
Each node contains:
- `name`
- `solid`
- `local frame` (node local coordinate system)
- `current transform` (variables to be solved)
- `anchors` (anchor points that can be referenced by constraints)
### 2) Anchor
Anchors are used to extract constrainable objects from geometry:
- `point`: 3D point
- `axis`: Directed line (point + direction)
- `plane`: Plane (point + normal)
- `frame`: Local coordinate system
Suggested built-in anchor sources:
- Bounding box: `bbox.min/max/center`
- Principal axes: `axis.x/y/z`
- Named faces: `face("top")`, `face("bottom")` (reuse existing tag mechanism)
### 3) Constraint
Constraints consist of:
- `type`
- `lhs anchor` / `rhs anchor`
- `value` (optional, e.g., distance)
- `priority` (hard/soft)
- `weight` (soft constraint weight)
### 4) Layout Container
Containers are constraint syntax sugar, compiled into a set of basic constraints:
- E.g., `stack([A,B,C], axis="z", gap=8)`
- `B.min_z = A.max_z + 8`
- `C.min_z = B.max_z + 8`
- Plus alignment constraints (e.g., XY centering)
## API Draft
```python
import simplecadapi as scad
from simplecadapi.constraints import Assembly, stack
asm = Assembly(name="shock_absorber")
sleeve = asm.add_part("sleeve", sleeve_solid)
rod = asm.add_part("rod", rod_solid)
spring = asm.add_part("spring", spring_solid)
asm.concentric(rod.axis("z"), sleeve.axis("z"))
asm.offset(rod.anchor("bottom"), sleeve.anchor("bottom"), 10.0)
asm.distance(rod.anchor("top"), sleeve.anchor("top"), min_value=5.0)
stack(
asm,
parts=[spring],
axis="z",
relative_to=sleeve,
align="center",
justify="start",
gap=8.0,
)
result = asm.solve()
solids = result.solids()
scad.export_step(solids, "shock_absorber_assembly.step")
```
## Solving Strategy (Layered)
### Layer A: Parsing and Normalization
- Convert constraints and container rules into residual equations.
- Map anchor references to real-time geometric query functions.
### Layer B: Graph Constraint Propagation (Fast Path)
- First perform topological solving for directly propagatable rigid constraints:
- Coaxial + offset + alignment can directly derive partial poses.
- Build dependency graph and perform incremental updates (dirty propagation).
### Layer C: Numerical Solving (Fallback)
- Use least squares solving for remaining degrees of freedom (hard constraints have highest priority).
- For over-constrained or contradictory constraints, output diagnostic reports:
- Conflicting constraint pairs
- Constraints with highest residuals
- Recommended relaxation items (downgrade from hard to soft)
## Result Model
`solve()` returns an object that should contain:
- `transforms`: Final pose for each part
- `solids()`: List of solids with poses applied
- `report`: Solving information
- Whether converged
- Number of iterations
- Maximum residual
- Conflict/over-constraint description
## Compatibility Strategy with Existing API
1. No changes to existing `operations.py` imperative interfaces.
2. New independent module (suggested `simplecadapi.constraints`).
3. Export still reuses `export_step` / `export_stl`, can directly export `result.solids()`.
## Milestone Plan
### M0 - Design and Feasibility Verification (Current Phase)
- Output this design document.
- Define minimum API surface.
- Select first batch of constraint types and diagnostic formats.
### M1 - Minimum Viable Prototype (MVP)
- `Assembly.add_part()`
- Anchors: `bbox` + `axis` + `face tag`
- Constraints: `concentric` + `offset` + `distance`
- Solving: Support single-chain assemblies (no loops)
### M2 - Flex-like Layout Containers
- `stack(axis, gap, align, justify)`
- Compile container rules into constraints
- Support simple incremental updates
### M3 - Diagnostics and Engineering
- Conflict localization and interpretable error messages
- Solving logs and visual output (text reports)
- Unit test coverage for typical assembly scenarios
### M4 - Advanced Capabilities
- Soft constraint weight system
- Complex constraint loops
- Performance optimization (caching, partitioned solving)
## Testing Recommendations
At least cover the following scenarios:
1. **Basic convergence**: Coaxial + offset + alignment, unique result.
2. **Under-constrained**: Clear warning when degrees of freedom are not locked.
3. **Over-constrained**: Conflict diagnostics when constraints are contradictory.
4. **Incremental updates**: Modifying a single parameter triggers only local recalculation.
5. **Export consistency**: `result.solids()` can directly export STEP/STL.
## Risks and Mitigations
- **Risk:** Constraint semantics too abstract, making the API hard to use.
**Mitigation:** Start with high-frequency assembly scenarios, providing only a few strongly semantic constraints.
- **Risk:** Numerical solving is unstable.
**Mitigation:** First do graph propagation fast path; numerical solving only handles remaining degrees of freedom.
- **Risk:** Conflict with existing user code.
**Mitigation:** New module isolation, disabled by default, strictly maintain backward compatibility.
## Conclusion
Migrating the Flexbox-style "declare relationships -> solve geometry" paradigm to the CAD SDK is feasible and can significantly improve assembly modeling efficiency and maintainability. It is recommended to use "assembly pose constraints + 1D container layout" as the Phase 1 entry point, deliver an MVP first, and then gradually enhance solving capabilities and diagnostic experience.

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