first commit
This commit is contained in:
@@ -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`
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"""Skill-local evolved cases for simplecad-self-evolve."""
|
||||
|
||||
from .evolve import *
|
||||
+6
@@ -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] = []
|
||||
+34
@@ -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.
|
||||
+31
@@ -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`
|
||||
+216
@@ -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`.
|
||||
+48
@@ -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
|
||||
```
|
||||
+119
@@ -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)*
|
||||
+13
@@ -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.
|
||||
+25
@@ -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.
|
||||
+23
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+65
@@ -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]))
|
||||
```
|
||||
+35
@@ -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.
|
||||
+35
@@ -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.
|
||||
+47
@@ -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")
|
||||
```
|
||||
+47
@@ -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")
|
||||
```
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+29
@@ -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.
|
||||
+13
@@ -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.
|
||||
+23
@@ -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.
|
||||
+65
@@ -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))
|
||||
```
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+13
@@ -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.
|
||||
+31
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+45
@@ -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)
|
||||
```
|
||||
+13
@@ -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.
|
||||
+25
@@ -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.
|
||||
+25
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+31
@@ -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.
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+25
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+13
@@ -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.
|
||||
+31
@@ -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.
|
||||
+31
@@ -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.
|
||||
+13
@@ -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.
|
||||
+29
@@ -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.
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+13
@@ -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.
|
||||
+37
@@ -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)
|
||||
```
|
||||
+13
@@ -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.
|
||||
+27
@@ -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.
|
||||
+13
@@ -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.
|
||||
+23
@@ -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.
|
||||
+99
@@ -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]
|
||||
```
|
||||
+36
@@ -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)
|
||||
```
|
||||
+213
@@ -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)
|
||||
+635
@@ -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
|
||||
+175
@@ -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
|
||||
+306
@@ -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
Reference in New Issue
Block a user