first commit

This commit is contained in:
2026-07-22 13:48:46 +08:00
commit c87751c3dc
2820 changed files with 726976 additions and 0 deletions
@@ -0,0 +1,119 @@
# SimpleCAD API Index
This index includes API docs generated from `operations.py`, `evolve.py`, `constraints.py`, and `ql.py`.
## Basic Creation
- [make_angle_arc_redge](make_angle_arc_redge.md) *(from operations.py)*
- [make_angle_arc_rwire](make_angle_arc_rwire.md) *(from operations.py)*
- [make_box_rscalarfield](make_box_rscalarfield.md) *(from field.py)*
- [make_box_rsolid](make_box_rsolid.md) *(from operations.py)*
- [make_capsule_rscalarfield](make_capsule_rscalarfield.md) *(from field.py)*
- [make_circle_redge](make_circle_redge.md) *(from operations.py)*
- [make_circle_rface](make_circle_rface.md) *(from operations.py)*
- [make_circle_rwire](make_circle_rwire.md) *(from operations.py)*
- [make_cone_rsolid](make_cone_rsolid.md) *(from operations.py)*
- [make_cylinder_rsolid](make_cylinder_rsolid.md) *(from operations.py)*
- [make_ellipsoid_rscalarfield](make_ellipsoid_rscalarfield.md) *(from field.py)*
- [make_face_from_wire_rface](make_face_from_wire_rface.md) *(from operations.py)*
- [make_field_surface_rsolid](make_field_surface_rsolid.md) *(from operations.py)*
- [make_helix_redge](make_helix_redge.md) *(from operations.py)*
- [make_helix_rwire](make_helix_rwire.md) *(from operations.py)*
- [make_line_redge](make_line_redge.md) *(from operations.py)*
- [make_point_rvertex](make_point_rvertex.md) *(from operations.py)*
- [make_polyline_rwire](make_polyline_rwire.md) *(from operations.py)*
- [make_rectangle_rface](make_rectangle_rface.md) *(from operations.py)*
- [make_rectangle_rwire](make_rectangle_rwire.md) *(from operations.py)*
- [make_segment_redge](make_segment_redge.md) *(from operations.py)*
- [make_segment_rwire](make_segment_rwire.md) *(from operations.py)*
- [make_sphere_rscalarfield](make_sphere_rscalarfield.md) *(from field.py)*
- [make_sphere_rsolid](make_sphere_rsolid.md) *(from operations.py)*
- [make_spline_redge](make_spline_redge.md) *(from operations.py)*
- [make_spline_rwire](make_spline_rwire.md) *(from operations.py)*
- [make_three_point_arc_redge](make_three_point_arc_redge.md) *(from operations.py)*
- [make_three_point_arc_rwire](make_three_point_arc_rwire.md) *(from operations.py)*
- [make_wire_from_edges_rwire](make_wire_from_edges_rwire.md) *(from operations.py)*
## Transforms
- [mirror_shape](mirror_shape.md) *(from operations.py)*
- [rotate_rscalarfield](rotate_rscalarfield.md) *(from field.py)*
- [rotate_shape](rotate_shape.md) *(from operations.py)*
- [translate_rscalarfield](translate_rscalarfield.md) *(from field.py)*
- [translate_shape](translate_shape.md) *(from operations.py)*
## 3D Operations
- [extrude_rsolid](extrude_rsolid.md) *(from operations.py)*
- [loft_rsolid](loft_rsolid.md) *(from operations.py)*
- [revolve_rsolid](revolve_rsolid.md) *(from operations.py)*
- [sweep_rsolid](sweep_rsolid.md) *(from operations.py)*
## Tagging and Selection
- [select_edges_by_tag](select_edges_by_tag.md) *(from operations.py)*
- [select_faces_by_tag](select_faces_by_tag.md) *(from operations.py)*
- [set_tag](set_tag.md) *(from operations.py)*
## Boolean Operations
- [cut_rsolidlist](cut_rsolidlist.md) *(from operations.py)*
- [intersect_rscalarfield](intersect_rscalarfield.md) *(from field.py)*
- [intersect_rsolidlist](intersect_rsolidlist.md) *(from operations.py)*
- [union_rscalarfield](union_rscalarfield.md) *(from field.py)*
- [union_rsolidlist](union_rsolidlist.md) *(from operations.py)*
## Export
- [export_step](export_step.md) *(from operations.py)*
- [export_stl](export_stl.md) *(from operations.py)*
## Advanced Features
- [chamfer_rsolid](chamfer_rsolid.md) *(from operations.py)*
- [fillet_rsolid](fillet_rsolid.md) *(from operations.py)*
- [helical_sweep_rsolid](helical_sweep_rsolid.md) *(from operations.py)*
- [shell_rsolid](shell_rsolid.md) *(from operations.py)*
## Evolve
- [make_n_hole_flange_rsolid](make_n_hole_flange_rsolid.md) *(from evolve.py)*
- [make_naca_propeller_blade_rsolid](make_naca_propeller_blade_rsolid.md) *(from evolve.py)*
- [make_threaded_rod_rsolid](make_threaded_rod_rsolid.md) *(from evolve.py)*
## Assembly Constraints
- [add_part_rassembly](add_part_rassembly.md) *(from constraints.py)*
- [clear_constraints_rassembly](clear_constraints_rassembly.md) *(from constraints.py)*
- [clone_assembly_rassembly](clone_assembly_rassembly.md) *(from constraints.py)*
- [constrain_coincident_rassembly](constrain_coincident_rassembly.md) *(from constraints.py)*
- [constrain_concentric_rassembly](constrain_concentric_rassembly.md) *(from constraints.py)*
- [constrain_distance_rassembly](constrain_distance_rassembly.md) *(from constraints.py)*
- [constrain_offset_rassembly](constrain_offset_rassembly.md) *(from constraints.py)*
- [make_assembly_rassembly](make_assembly_rassembly.md) *(from constraints.py)*
- [rotate_part_rassembly](rotate_part_rassembly.md) *(from constraints.py)*
- [solve_assembly_rresult](solve_assembly_rresult.md) *(from constraints.py)*
- [stack](stack.md) *(from constraints.py)*
- [stack_rassembly](stack_rassembly.md) *(from constraints.py)*
- [translate_part_rassembly](translate_part_rassembly.md) *(from constraints.py)*
## Other
- [and_](and_.md) *(from ql.py)*
- [bounds_rbbox](bounds_rbbox.md) *(from field.py)*
- [eval_rarray](eval_rarray.md) *(from field.py)*
- [eval_rscalar](eval_rscalar.md) *(from field.py)*
- [geo](geo.md) *(from ql.py)*
- [linear_pattern_rsolidlist](linear_pattern_rsolidlist.md) *(from operations.py)*
- [meta](meta.md) *(from ql.py)*
- [not_](not_.md) *(from ql.py)*
- [or_](or_.md) *(from ql.py)*
- [radial_pattern_rsolidlist](radial_pattern_rsolidlist.md) *(from operations.py)*
- [render_screenshot_rpath](render_screenshot_rpath.md) *(from operations.py)*
- [scale_rscalarfield](scale_rscalarfield.md) *(from field.py)*
- [select](select.md) *(from ql.py)*
- [smooth_subtract_rscalarfield](smooth_subtract_rscalarfield.md) *(from field.py)*
- [smooth_union_rscalarfield](smooth_union_rscalarfield.md) *(from field.py)*
- [subtract_rscalarfield](subtract_rscalarfield.md) *(from field.py)*
- [tag](tag.md) *(from ql.py)*
- [value](value.md) *(from ql.py)*
@@ -0,0 +1,13 @@
# add_part_rassembly
## API Definition
```python
def add_part_rassembly(assembly: Assembly, name: str, solid: Solid, parent: Optional[Union[str, PartHandle]] = None, local_transform: Optional[Union[np.ndarray, Sequence[Sequence[float]]]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a part in assembly space and return a new assembly.
@@ -0,0 +1,25 @@
# and_
## API Definition
```python
def and_(*predicates: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build an AND-composed predicate.
Q.and_(Q.tag("face.top"), Q.tag("role.mounting_surface"))
## Parameters
### *predicates
- **Description**: Any number of predicates.
## Returns
Callable[[Any], bool]: Combined predicate.
@@ -0,0 +1,23 @@
# bounds_rbbox
## API Definition
```python
def bounds_rbbox(field: ScalarField) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]
```
*Source: field.py*
## Description
Compute the axis-aligned bounding box of a scalar field.
## Parameters
### field
- **Description**: Scalar field.
## Returns
Tuple[min_xyz, max_xyz]: Bounding box.
@@ -0,0 +1,13 @@
# chamfer_rsolid
## API Definition
```python
def chamfer_rsolid(solid: Solid, edges: List[Edge], distance: float) -> Solid
```
*Source: operations.py*
## Description
Apply chamfers to selected solid edges.
@@ -0,0 +1,13 @@
# clear_constraints_rassembly
## API Definition
```python
def clear_constraints_rassembly(assembly: Assembly) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: clear constraints and return a new assembly.
@@ -0,0 +1,13 @@
# clone_assembly_rassembly
## API Definition
```python
def clone_assembly_rassembly(assembly: Assembly) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: clone one assembly object into another.
@@ -0,0 +1,13 @@
# constrain_coincident_rassembly
## API Definition
```python
def constrain_coincident_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a coincident constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_concentric_rassembly
## API Definition
```python
def constrain_concentric_rassembly(assembly: Assembly, reference: AxisAnchor, moving: AxisAnchor, same_direction: bool = False) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a concentric constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_distance_rassembly
## API Definition
```python
def constrain_distance_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor, distance: float, fallback_axis: AxisLike = 'x') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add a point-distance constraint and return a new assembly.
@@ -0,0 +1,13 @@
# constrain_offset_rassembly
## API Definition
```python
def constrain_offset_rassembly(assembly: Assembly, reference: PointAnchor, moving: PointAnchor, distance: float, axis: AxisLike = 'z') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: add an axial offset constraint and return a new assembly.
@@ -0,0 +1,65 @@
# cut_rsolidlist
## API Definition
```python
def cut_rsolidlist(*solids: Union[Solid, Sequence[Solid]]) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean difference of solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
`cut_rsolidlist(base, [tool_a, tool_b])` is valid input.
If an earlier union returned multiple solids, keep that list and process each
solid intentionally instead of collapsing it to `result[0]` without proof.
If a later step truly requires one solid, first verify `len(results) == 1`.
When a preceding union produced multiple tangent-only solids, adjust the part
placement so the intended bodies overlap slightly, re-run the union, and only
then unwrap the single result.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing; the first solid is the base, the rest are subtracted in order.
## Returns
List[Solid]: A list containing the cut result solid, or an empty list when
there is no valid input. The result is returned as a list for consistency
with other boolean operations.
## Examples
### Example 1
```python
body = make_box_rsolid(12, 4, 4, bottom_face_center=(0, 0, 0))
slot = make_box_rsolid(2, 2, 6, bottom_face_center=(2, 1, -1))
relief = make_cylinder_rsolid(radius=0.8, height=6, center=(8, 2, 2))
```
### Example 2
```python
results = cut_rsolidlist(body, [slot, relief])
print(f"Cut result count: {len(results)}")
```
### Example 3
```python
# If a previous union returned multiple solids, keep the list and cut each part.
tangent_parts = union_rsolidlist(
body,
[
make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0)),
make_sphere_rsolid(2.0, center=(14.0, 2.0, 2.0)),
],
)
trimmed_parts = []
for part in tangent_parts:
trimmed_parts.extend(cut_rsolidlist(part, [slot, relief]))
```
@@ -0,0 +1,35 @@
# eval_rarray
## API Definition
```python
def eval_rarray(field: ScalarField, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray
```
*Source: field.py*
## Description
Evaluate a scalar field on arrays of points.
## Parameters
### field
- **Description**: Scalar field.
### xs
- **Description**: Array of X coordinates.
### ys
- **Description**: Array of Y coordinates.
### zs
- **Description**: Array of Z coordinates.
## Returns
np.ndarray: Array of field values.
@@ -0,0 +1,35 @@
# eval_rscalar
## API Definition
```python
def eval_rscalar(field: ScalarField, x: float, y: float, z: float) -> float
```
*Source: field.py*
## Description
Evaluate a scalar field at a single point.
## Parameters
### field
- **Description**: Scalar field.
### x
- **Description**: X coordinate.
### y
- **Description**: Y coordinate.
### z
- **Description**: Z coordinate.
## Returns
float: Field value.
@@ -0,0 +1,47 @@
# export_step
## API Definition
```python
def export_step(shapes: Union[AnyShape, Sequence[AnyShape]], filename: str) -> None
```
*Source: operations.py*
## Description
Export shapes to STEP.
Use this function when you want to export one shape or many shapes into the
same STEP file. Passing `List[Solid]` is valid and often preferred when a
previous boolean operation returned multiple solids.
## Parameters
### shapes
- **Description**: A single exportable shape or any nested sequence of exportable shapes. Lists of Solid are supported directly, including list results returned by boolean operations.
### filename
- **Description**: Output STEP file path.
## Returns
None: Writes the provided shapes into one STEP file.
## Examples
### Example 1
```python
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
```
### Example 2
```python
# Export the full list directly; no need to collapse to body_parts[0].
export_step(body_parts, "rounded_bar.step")
```
@@ -0,0 +1,47 @@
# export_stl
## API Definition
```python
def export_stl(shapes: Union[AnyShape, Sequence[AnyShape]], filename: str) -> None
```
*Source: operations.py*
## Description
Export shapes to STL.
Use this function when you want to export one solid or many solids/faces into
the same STL file. Passing `List[Solid]` is valid and often preferred when a
previous boolean operation returned multiple solids.
## Parameters
### shapes
- **Description**: A single Solid or Face, or any nested sequence of Solid/Face. Lists of Solid are supported directly, including list results returned by boolean operations.
### filename
- **Description**: Output STL file path.
## Returns
None: Writes the provided shapes into one STL file.
## Examples
### Example 1
```python
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
```
### Example 2
```python
# Export the list result directly.
export_stl(body_parts, "rounded_bar.stl")
```
@@ -0,0 +1,13 @@
# extrude_rsolid
## API Definition
```python
def extrude_rsolid(profile: Union[Wire, Face], direction: Tuple[float, float, float], distance: float) -> Solid
```
*Source: operations.py*
## Description
Create a solid by extruding a profile.
@@ -0,0 +1,13 @@
# fillet_rsolid
## API Definition
```python
def fillet_rsolid(solid: Solid, edges: List[Edge], radius: float) -> Solid
```
*Source: operations.py*
## Description
Apply fillets to selected solid edges.
@@ -0,0 +1,29 @@
# geo
## API Definition
```python
def geo(field: str, default: Any = None) -> KeyFn
```
*Source: ql.py*
## Description
Convenience builder for a `geo` metadata getter.
Q.select(items).order_by(Q.geo("height"))
## Parameters
### field
- **Description**: `geo` field name, such as `type` or `height`.
### default
- **Description**: Default value when lookup fails.
## Returns
Callable[[Any], Any]: Getter function.
@@ -0,0 +1,13 @@
# helical_sweep_rsolid
## API Definition
```python
def helical_sweep_rsolid(profile: Wire, pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a solid by sweeping a profile along a helical path.
@@ -0,0 +1,23 @@
# intersect_rscalarfield
## API Definition
```python
def intersect_rscalarfield(*fields: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create an intersection scalar field.
## Parameters
### *fields
- **Description**: Input scalar fields.
## Returns
ScalarField: Intersection scalar field.
@@ -0,0 +1,65 @@
# intersect_rsolidlist
## API Definition
```python
def intersect_rsolidlist(*solids: Union[Solid, Sequence[Solid]]) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean intersection of solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
`intersect_rsolidlist(body, [clip_a, clip_b])` is valid input.
If an earlier union returned multiple solids, keep that list and intersect
each solid intentionally instead of collapsing it to `result[0]`.
If a later step truly requires one solid, first verify `len(results) == 1`.
When a preceding union produced multiple tangent-only solids, adjust the part
placement so the intended bodies overlap slightly, re-run the union, and only
then unwrap the single result.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing.
## Returns
List[Solid]: A list containing the intersection result, or an empty list if
the solids do not overlap. The result is returned as a list for
consistency with other boolean operations.
## Examples
### Example 1
```python
body = make_box_rsolid(12, 4, 4, bottom_face_center=(0, 0, 0))
clip_a = make_box_rsolid(8, 4, 4, bottom_face_center=(2, 0, 0))
clip_b = make_box_rsolid(6, 6, 6, bottom_face_center=(3, -1, -1))
```
### Example 2
```python
results = intersect_rsolidlist(body, [clip_a, clip_b])
print(f"Intersect result count: {len(results)}")
```
### Example 3
```python
# A previous union may return multiple solids; keep the list and intersect each part.
tangent_parts = union_rsolidlist(
body,
[
make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0)),
make_sphere_rsolid(2.0, center=(14.0, 2.0, 2.0)),
],
)
clipped_parts = []
for part in tangent_parts:
clipped_parts.extend(intersect_rsolidlist(part, clip_a))
```
@@ -0,0 +1,13 @@
# linear_pattern_rsolidlist
## API Definition
```python
def linear_pattern_rsolidlist(shape: AnyShape, direction: Tuple[float, float, float], count: int, spacing: float) -> List[Solid]
```
*Source: operations.py*
## Description
Create a linear pattern of solids.
@@ -0,0 +1,13 @@
# loft_rsolid
## API Definition
```python
def loft_rsolid(profiles: List[Wire], ruled: bool = False) -> Solid
```
*Source: operations.py*
## Description
Create a solid by lofting multiple profiles.
@@ -0,0 +1,13 @@
# make_angle_arc_redge
## API Definition
```python
def make_angle_arc_redge(center: Tuple[float, float, float], radius: float, start_angle: float, end_angle: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create an arc edge from a center, radius, and angle range.
@@ -0,0 +1,13 @@
# make_angle_arc_rwire
## API Definition
```python
def make_angle_arc_rwire(center: Tuple[float, float, float], radius: float, start_angle: float, end_angle: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing an arc defined by a center, radius, and angle range.
@@ -0,0 +1,13 @@
# make_assembly_rassembly
## API Definition
```python
def make_assembly_rassembly(parts: Sequence[Tuple[str, Solid]], name: str = 'assembly', parents: Optional[Dict[str, str]] = None, local_transforms: Optional[Dict[str, Union[np.ndarray, Sequence[Sequence[float]]]]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-1 mapping: lift a parameter description into an assembly object.
@@ -0,0 +1,27 @@
# make_box_rscalarfield
## API Definition
```python
def make_box_rscalarfield(center: Tuple[float, float, float], size: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Create an axis-aligned box scalar field.
## Parameters
### center
- **Description**: Box center coordinates `(x, y, z)`.
### size
- **Description**: Box size `(sx, sy, sz)`.
## Returns
ScalarField: Box scalar field.
@@ -0,0 +1,13 @@
# make_box_rsolid
## API Definition
```python
def make_box_rsolid(width: float, height: float, depth: float, bottom_face_center: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a box solid.
@@ -0,0 +1,31 @@
# make_capsule_rscalarfield
## API Definition
```python
def make_capsule_rscalarfield(p0: Tuple[float, float, float], p1: Tuple[float, float, float], radius: float) -> ScalarField
```
*Source: field.py*
## Description
Create a capsule scalar field.
## Parameters
### p0
- **Description**: First endpoint coordinates.
### p1
- **Description**: Second endpoint coordinates.
### radius
- **Description**: Capsule radius.
## Returns
ScalarField: Capsule scalar field.
@@ -0,0 +1,13 @@
# make_circle_redge
## API Definition
```python
def make_circle_redge(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create a circular edge.
@@ -0,0 +1,13 @@
# make_circle_rface
## API Definition
```python
def make_circle_rface(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a circular face.
@@ -0,0 +1,13 @@
# make_circle_rwire
## API Definition
```python
def make_circle_rwire(center: Tuple[float, float, float], radius: float, normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a circular wire.
@@ -0,0 +1,13 @@
# make_cone_rsolid
## API Definition
```python
def make_cone_rsolid(bottom_radius: float, height: float, top_radius: float = 0.0, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a cone or truncated cone solid.
@@ -0,0 +1,13 @@
# make_cylinder_rsolid
## API Definition
```python
def make_cylinder_rsolid(radius: float, height: float, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
```
*Source: operations.py*
## Description
Create a cylinder solid.
@@ -0,0 +1,27 @@
# make_ellipsoid_rscalarfield
## API Definition
```python
def make_ellipsoid_rscalarfield(center: Tuple[float, float, float], radii: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Create an ellipsoid scalar field.
## Parameters
### center
- **Description**: Ellipsoid center coordinates `(x, y, z)`.
### radii
- **Description**: Radii `(rx, ry, rz)`.
## Returns
ScalarField: Ellipsoid scalar field.
@@ -0,0 +1,13 @@
# make_face_from_wire_rface
## API Definition
```python
def make_face_from_wire_rface(wire: Wire, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a face from a closed wire.
@@ -0,0 +1,13 @@
# make_field_surface_rsolid
## API Definition
```python
def make_field_surface_rsolid(field, bounds: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None, resolution: Tuple[int, int, int] = (24, 24, 24), iso: float = 0.0, cap_bounds: bool = True) -> Solid
```
*Source: operations.py*
## Description
Build a closed solid from a scalar field isosurface.
@@ -0,0 +1,13 @@
# make_helix_redge
## API Definition
```python
def make_helix_redge(pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Edge
```
*Source: operations.py*
## Description
Create a helix edge.
@@ -0,0 +1,13 @@
# make_helix_rwire
## API Definition
```python
def make_helix_rwire(pitch: float, height: float, radius: float, center: Tuple[float, float, float] = (0, 0, 0), dir: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a helix wire.
@@ -0,0 +1,13 @@
# make_line_redge
## API Definition
```python
def make_line_redge(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Create a straight edge between two points.
@@ -0,0 +1,13 @@
# make_n_hole_flange_rsolid
## API Definition
```python
def make_n_hole_flange_rsolid(flange_outer_diameter = 120.0, flange_inner_diameter = 60.0, flange_thickness = 15.0, boss_outer_diameter = 80.0, boss_height = 5.0, hole_diameter = 8.0, hole_circle_diameter = 100.0, hole_count = 8, chamfer_size = 1.0) -> Solid
```
*Source: evolve.py*
## Description
Create an n-hole flange with a raised boss ring and optional chamfers. The center of the bottom face is placed at the origin.
@@ -0,0 +1,13 @@
# make_naca_propeller_blade_rsolid
## API Definition
```python
def make_naca_propeller_blade_rsolid(blade_length = 5.0, root_chord = 1.5, tip_chord = 0.3, total_twist_angle = 45.0, num_sections = 7, t_c = 0.16) -> Solid
```
*Source: evolve.py*
## Description
Create a single propeller blade solid from a twisted NACA 0016 profile. The blade root starts at the origin and extends along +Z.
@@ -0,0 +1,13 @@
# make_point_rvertex
## API Definition
```python
def make_point_rvertex(x: float, y: float, z: float) -> Vertex
```
*Source: operations.py*
## Description
Create a point in 3D space and return it as a vertex.
@@ -0,0 +1,13 @@
# make_polyline_rwire
## API Definition
```python
def make_polyline_rwire(points: List[Tuple[float, float, float]], closed: bool = False) -> Wire
```
*Source: operations.py*
## Description
Create a polyline wire from a point list.
@@ -0,0 +1,13 @@
# make_rectangle_rface
## API Definition
```python
def make_rectangle_rface(width: float, height: float, center: Tuple[float, float, float] = (0, 0, 0), normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
```
*Source: operations.py*
## Description
Create a rectangular face.
@@ -0,0 +1,13 @@
# make_rectangle_rwire
## API Definition
```python
def make_rectangle_rwire(width: float, height: float, center: Tuple[float, float, float] = (0, 0, 0), normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
```
*Source: operations.py*
## Description
Create a rectangular wire.
@@ -0,0 +1,13 @@
# make_segment_redge
## API Definition
```python
def make_segment_redge(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Alias of `make_line_redge` that returns a straight edge.
@@ -0,0 +1,13 @@
# make_segment_rwire
## API Definition
```python
def make_segment_rwire(start: Tuple[float, float, float], end: Tuple[float, float, float]) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing a single straight segment.
@@ -0,0 +1,27 @@
# make_sphere_rscalarfield
## API Definition
```python
def make_sphere_rscalarfield(center: Tuple[float, float, float], radius: float) -> ScalarField
```
*Source: field.py*
## Description
Create a spherical scalar field.
## Parameters
### center
- **Description**: Sphere center coordinates `(x, y, z)`.
### radius
- **Description**: Sphere radius.
## Returns
ScalarField: Sphere scalar field.
@@ -0,0 +1,13 @@
# make_sphere_rsolid
## API Definition
```python
def make_sphere_rsolid(radius: float, center: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a sphere solid.
@@ -0,0 +1,13 @@
# make_spline_redge
## API Definition
```python
def make_spline_redge(points: List[Tuple[float, float, float]], tangents: Optional[List[Tuple[float, float, float]]] = None) -> Edge
```
*Source: operations.py*
## Description
Create a spline edge through control points.
@@ -0,0 +1,13 @@
# make_spline_rwire
## API Definition
```python
def make_spline_rwire(points: List[Tuple[float, float, float]], tangents: Optional[List[Tuple[float, float, float]]] = None, closed: bool = False) -> Wire
```
*Source: operations.py*
## Description
Create a spline wire through control points.
@@ -0,0 +1,13 @@
# make_threaded_rod_rsolid
## API Definition
```python
def make_threaded_rod_rsolid(thread_diameter = 8.0, thread_length = 20.0, total_length = 30.0, thread_pitch = 1.25, thread_start_position = 0.0, chamfer_size = 0.5) -> Solid
```
*Source: evolve.py*
## Description
Create a threaded rod with configurable rod length, thread span, and pitch. The top center is placed at the origin and the rod extends in -Z.
@@ -0,0 +1,13 @@
# make_three_point_arc_redge
## API Definition
```python
def make_three_point_arc_redge(start: Tuple[float, float, float], middle: Tuple[float, float, float], end: Tuple[float, float, float]) -> Edge
```
*Source: operations.py*
## Description
Create an arc edge from three points.
@@ -0,0 +1,13 @@
# make_three_point_arc_rwire
## API Definition
```python
def make_three_point_arc_rwire(start: Tuple[float, float, float], middle: Tuple[float, float, float], end: Tuple[float, float, float]) -> Wire
```
*Source: operations.py*
## Description
Create a wire containing an arc defined by three points.
@@ -0,0 +1,13 @@
# make_wire_from_edges_rwire
## API Definition
```python
def make_wire_from_edges_rwire(edges: List[Edge]) -> Wire
```
*Source: operations.py*
## Description
Create a wire from a list of connected edges.
@@ -0,0 +1,45 @@
# meta
## API Definition
```python
def meta(path: str, op: str, value: Any) -> Predicate
```
*Source: ql.py*
## Description
Build a metadata-based predicate.
Q.meta("geo.type", "==", "box")
## Parameters
### path
- **Description**: Metadata path, for example `geo.type`.
### op
- **Description**: Comparison operator. Supports `==`, `!=`, `>`, `>=`, `<`, and `<=`.
### value
- **Description**: Comparison target value.
## Returns
Callable[[Any], bool]: Predicate function.
## Raises
- **TypeError**: If op is not a string.
- **ValueError**: If the operator is unsupported.
## Examples
```python
pred = Q.meta("geo.size.x", ">", 1.0)
matched = pred(obj)
```
@@ -0,0 +1,13 @@
# mirror_shape
## API Definition
```python
def mirror_shape(shape: AnyShape, plane_origin: Tuple[float, float, float], plane_normal: Tuple[float, float, float]) -> AnyShape
```
*Source: operations.py*
## Description
Mirror a shape across a plane.
@@ -0,0 +1,25 @@
# not_
## API Definition
```python
def not_(predicate: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build a NOT predicate.
Q.not_(Q.tag("state.*"))
## Parameters
### predicate
- **Description**: A single predicate.
## Returns
Callable[[Any], bool]: Negated predicate.
@@ -0,0 +1,25 @@
# or_
## API Definition
```python
def or_(*predicates: Predicate) -> Predicate
```
*Source: ql.py*
## Description
Build an OR-composed predicate.
Q.or_(Q.tag("face.top"), Q.tag("face.bottom"))
## Parameters
### *predicates
- **Description**: Any number of predicates.
## Returns
Callable[[Any], bool]: Combined predicate.
@@ -0,0 +1,13 @@
# radial_pattern_rsolidlist
## API Definition
```python
def radial_pattern_rsolidlist(shape: AnyShape, center: Tuple[float, float, float], axis: Tuple[float, float, float], count: int, total_rotation_angle: float) -> List[Solid]
```
*Source: operations.py*
## Description
Create a radial pattern of solids.
@@ -0,0 +1,13 @@
# render_screenshot_rpath
## API Definition
```python
def render_screenshot_rpath(shapes: Union[Solid, Sequence[Solid]], output_path: str, highlight_tags: Optional[Sequence[str]] = None, tag_labels: Optional[Dict[str, str]] = None, image_size: Tuple[int, int] = (1400, 900), view: Union[Tuple[float, float], str] = 'auto', show_axes: bool = True, show_legend: bool = True, zoom: float = 4.0) -> str
```
*Source: operations.py*
## Description
Render a screenshot of shapes and save it to a file.
@@ -0,0 +1,13 @@
# revolve_rsolid
## API Definition
```python
def revolve_rsolid(profile: Union[Wire, Face], axis: Tuple[float, float, float] = (0, 0, 1), angle: float = 360, origin: Tuple[float, float, float] = (0, 0, 0)) -> Solid
```
*Source: operations.py*
## Description
Create a solid by revolving a profile around an axis.
@@ -0,0 +1,13 @@
# rotate_part_rassembly
## API Definition
```python
def rotate_part_rassembly(assembly: Assembly, part: Union[str, PartHandle], angle_deg: float, axis: AxisLike = 'z', origin: Vec3Like = (0.0, 0.0, 0.0), frame: Literal['world', 'local'] = 'world') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: rotate a part and return a new assembly.
@@ -0,0 +1,31 @@
# rotate_rscalarfield
## API Definition
```python
def rotate_rscalarfield(field: ScalarField, axis: Tuple[float, float, float], angle_degrees: float) -> ScalarField
```
*Source: field.py*
## Description
Rotate a scalar field around the origin.
## Parameters
### field
- **Description**: Input scalar field.
### axis
- **Description**: Rotation axis vector `(x, y, z)`.
### angle_degrees
- **Description**: Rotation angle in degrees.
## Returns
ScalarField: Rotated scalar field.
@@ -0,0 +1,13 @@
# rotate_shape
## API Definition
```python
def rotate_shape(shape: AnyShape, angle: float, axis: Tuple[float, float, float] = (0, 0, 1), origin: Tuple[float, float, float] = (0, 0, 0)) -> AnyShape
```
*Source: operations.py*
## Description
Rotate a shape around an axis.
@@ -0,0 +1,27 @@
# scale_rscalarfield
## API Definition
```python
def scale_rscalarfield(field: ScalarField, factors: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Scale a scalar field around the origin.
## Parameters
### field
- **Description**: Input scalar field.
### factors
- **Description**: Scale factors `(sx, sy, sz)`.
## Returns
ScalarField: Scaled scalar field.
@@ -0,0 +1,25 @@
# select
## API Definition
```python
def select(items: Iterable[Any]) -> Query
```
*Source: ql.py*
## Description
Create a query object.
Q.select(items).where(Q.tag("face.top")).first()
## Parameters
### items
- **Description**: Any iterable.
## Returns
Query: Query object.
@@ -0,0 +1,13 @@
# select_edges_by_tag
## API Definition
```python
def select_edges_by_tag(shape: Union[Face, Solid], tag: str) -> List[Edge]
```
*Source: operations.py*
## Description
Select edges by tag.
@@ -0,0 +1,13 @@
# select_faces_by_tag
## API Definition
```python
def select_faces_by_tag(solid: Solid, tag: str) -> List[Face]
```
*Source: operations.py*
## Description
Select faces by tag.
@@ -0,0 +1,13 @@
# set_tag
## API Definition
```python
def set_tag(shape: AnyShape, tag: str) -> AnyShape
```
*Source: operations.py*
## Description
Attach a tag to a shape.
@@ -0,0 +1,13 @@
# shell_rsolid
## API Definition
```python
def shell_rsolid(solid: Solid, faces_to_remove: List[Face], thickness: float) -> Solid
```
*Source: operations.py*
## Description
Shell a solid to create a hollow part.
@@ -0,0 +1,31 @@
# smooth_subtract_rscalarfield
## API Definition
```python
def smooth_subtract_rscalarfield(a: ScalarField, b: ScalarField, k: float) -> ScalarField
```
*Source: field.py*
## Description
Create a smooth subtraction scalar field.
## Parameters
### a
- **Description**: Minuend scalar field.
### b
- **Description**: Subtrahend scalar field.
### k
- **Description**: Smoothing factor, which must be positive.
## Returns
ScalarField: Smooth subtraction scalar field.
@@ -0,0 +1,31 @@
# smooth_union_rscalarfield
## API Definition
```python
def smooth_union_rscalarfield(a: ScalarField, b: ScalarField, k: float) -> ScalarField
```
*Source: field.py*
## Description
Create a smooth union scalar field.
## Parameters
### a
- **Description**: Scalar field A.
### b
- **Description**: Scalar field B.
### k
- **Description**: Smoothing factor, which must be positive.
## Returns
ScalarField: Smooth union scalar field.
@@ -0,0 +1,13 @@
# solve_assembly_rresult
## API Definition
```python
def solve_assembly_rresult(assembly: Assembly, max_iterations: int = 30, tolerance: float = 1e-06) -> AssemblyResult
```
*Source: constraints.py*
## Description
Type-2 mapping: map an assembly to a solve result without mutating it.
@@ -0,0 +1,29 @@
# stack
## API Definition
```python
def stack(assembly: Assembly, parts: Sequence[Union[str, PartHandle]], axis: str = 'z', gap: float = 0.0, align: Literal['center', 'start', 'end'] = 'center', justify: Literal['start', 'center', 'end', 'space-between'] = 'start', bounds: Optional[Tuple[PointAnchor, PointAnchor]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Declaratively stack multiple parts along the specified axis.
Semantics:
- sequential stacking: part i is placed after part i-1 with the given gap
- cross-axis alignment: the other two axes are aligned according to `align`
- main-axis distribution: `justify` controls how the whole stack is placed
within the bounds
BBox-first note:
- This function uses axis-aligned bounding-box (AABB) anchors such as
`bbox.top` and `bbox.bottom` to approximate Flexbox-like box semantics.
- For parts with large rotations, the AABB changes with pose, so the layout
result changes as well. This is expected in the current MVP stage.
Note:
- This is container-level sugar that compiles into a set of `offset(...)`
constraints internally.
@@ -0,0 +1,13 @@
# stack_rassembly
## API Definition
```python
def stack_rassembly(assembly: Assembly, parts: Sequence[Union[str, PartHandle]], axis: str = 'z', gap: float = 0.0, align: Literal['center', 'start', 'end'] = 'center', justify: Literal['start', 'center', 'end', 'space-between'] = 'start', bounds: Optional[Tuple[PointAnchor, PointAnchor]] = None) -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: apply a stack layout and return a new assembly.
@@ -0,0 +1,27 @@
# subtract_rscalarfield
## API Definition
```python
def subtract_rscalarfield(a: ScalarField, b: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create a subtraction scalar field.
## Parameters
### a
- **Description**: Minuend scalar field.
### b
- **Description**: Subtrahend scalar field.
## Returns
ScalarField: Subtraction scalar field.
@@ -0,0 +1,13 @@
# sweep_rsolid
## API Definition
```python
def sweep_rsolid(profile: Face, path: Wire, is_frenet: bool = False) -> Solid
```
*Source: operations.py*
## Description
Create a solid by sweeping a profile along a path.
@@ -0,0 +1,37 @@
# tag
## API Definition
```python
def tag(pattern: str) -> Predicate
```
*Source: ql.py*
## Description
Build a tag-based predicate.
`Q.tag("face.top")` or `Q.tag("role.*")`.
## Parameters
### pattern
- **Description**: Tag matching pattern. Supports a trailing `*` wildcard.
## Returns
Callable[[Any], bool]: Predicate function.
## Raises
- **TypeError**: If pattern is not a string.
- **ValueError**: If the wildcard position is invalid.
## Examples
```python
pred = Q.tag("role.*")
matched = pred(obj)
```
@@ -0,0 +1,13 @@
# translate_part_rassembly
## API Definition
```python
def translate_part_rassembly(assembly: Assembly, part: Union[str, PartHandle], vector: Vec3Like, frame: Literal['world', 'local'] = 'world') -> Assembly
```
*Source: constraints.py*
## Description
Type-2 mapping: translate a part and return a new assembly.
@@ -0,0 +1,27 @@
# translate_rscalarfield
## API Definition
```python
def translate_rscalarfield(field: ScalarField, offset: Tuple[float, float, float]) -> ScalarField
```
*Source: field.py*
## Description
Translate a scalar field.
## Parameters
### field
- **Description**: Input scalar field.
### offset
- **Description**: Translation vector `(dx, dy, dz)`.
## Returns
ScalarField: Translated scalar field.
@@ -0,0 +1,13 @@
# translate_shape
## API Definition
```python
def translate_shape(shape: AnyShape, vector: Tuple[float, float, float]) -> AnyShape
```
*Source: operations.py*
## Description
Translate a shape by an offset vector.
@@ -0,0 +1,23 @@
# union_rscalarfield
## API Definition
```python
def union_rscalarfield(*fields: ScalarField) -> ScalarField
```
*Source: field.py*
## Description
Create a union scalar field.
## Parameters
### *fields
- **Description**: Input scalar fields.
## Returns
ScalarField: Union scalar field.
@@ -0,0 +1,99 @@
# union_rsolidlist
## API Definition
```python
def union_rsolidlist(
*solids: Union[Solid, Sequence[Solid]],
clean: bool = True,
glue: bool = True,
tol: Optional[float] = None,
) -> List[Solid]
```
*Source: operations.py*
## Description
Compute the boolean union of one or more solids.
All boolean operations (union/cut/intersect) accept a mix of Solid and
sequences; results are always returned as a list of Solid.
Keep the list result unless you have explicitly verified `len(result) == 1`.
SimpleCAD enables glue mode by default and applies a conservative internal fuzzy
tolerance, so normal modeling code does not need to tune boolean parameters.
By default this follows CadQuery's union flow: perform one OCC fuse across the
input solids, then call `clean()` to unify same-domain faces when possible.
Touching-but-not-intersecting inputs can legitimately return multiple solids.
If that happens, keep using the list: pass it directly into later union calls,
or iterate over the solids for later cut/intersect steps.
When the returned solids are still separated by more than the active tolerance,
SimpleCAD prints a stdout warning explaining that the objects do not touch and
that their gap exceeds `tol`.
If you truly need exactly one merged solid, you must check the list length
before using `result[0]`.
## Parameters
### solids
- **Description**: One or more Solid objects or sequences of Solid. Nested sequences are flattened before processing.
### clean
- **Description**: Call CadQuery's `clean()` after the union to remove splitter edges and unify same-domain faces when possible.
### glue
- **Description**: Enable OCC glue mode for touching or partially overlapping inputs. Defaults to `True` for SimpleCAD's standard union behavior.
### tol
- **Description**: Optional fuzzy-boolean tolerance passed to the OCC union kernel. When omitted, SimpleCAD derives a conservative scale-aware value automatically.
## Returns
List[Solid]: Resulting solids after union attempts. Solids that can be fused
are merged; disjoint or tangent-only contacts remain separate, so the
list may contain multiple solids.
## Examples
### Example 1
```python
# Rounded-bar style input: end caps only touch the center body.
main_body = make_box_rsolid(10, 4, 4, bottom_face_center=(0, 0, 0))
left_cap = make_sphere_rsolid(2.0, center=(-2.0, 2.0, 2.0))
right_cap = make_sphere_rsolid(2.0, center=(12.0, 2.0, 2.0))
```
### Example 2
```python
body_parts = union_rsolidlist(main_body, [left_cap, right_cap])
print(f"Union result count: {len(body_parts)}")
# This is acceptable: tangent-only contact can stay as multiple solids.
for solid in body_parts:
print(f"- volume: {solid.get_volume():.6f}")
```
### Example 3
```python
# Keep using the returned list in later boolean steps.
rib = make_box_rsolid(2, 4, 4, bottom_face_center=(4, 0, 0))
combined_parts = union_rsolidlist(body_parts, rib)
print(f"Combined result count: {len(combined_parts)}")
```
### Example 4
```python
# Only unwrap to one solid after an explicit length check.
left_cap_embedded = make_sphere_rsolid(2.0, center=(-1.8, 2.0, 2.0))
right_cap_embedded = make_sphere_rsolid(2.0, center=(11.8, 2.0, 2.0))
merged = union_rsolidlist(main_body, [left_cap_embedded, right_cap_embedded])
if len(merged) != 1:
raise ValueError(
"Adjust part placement so each cap overlaps the body slightly before "
"using merged[0]."
)
final_body = merged[0]
```
@@ -0,0 +1,36 @@
# value
## API Definition
```python
def value(path: str, default: Any = None) -> KeyFn
```
*Source: ql.py*
## Description
Build a value getter for sorting or projection.
Q.select(items).order_by(Q.value("geo.height"))
## Parameters
### path
- **Description**: Metadata path, for example `geo.height`.
### default
- **Description**: Default value when lookup fails.
## Returns
Callable[[Any], Any]: Getter function.
## Examples
```python
key = Q.value("geo.height", 0.0)
height = key(obj)
```
@@ -0,0 +1,213 @@
# SimpleCAD API Core Classes Documentation
This directory contains detailed documentation for all core classes of SimpleCAD API.
## Core Classes Overview
SimpleCAD API provides a complete set of geometric modeling classes, from basic points, lines, and surfaces to complex solids and compounds. Each class has rich functionality and a flexible tag management system.
### Basic Classes
#### [CoordinateSystem - Coordinate System](coordinate_system.md)
A 3D coordinate system class for defining and managing local coordinate systems, supporting coordinate transformations and integration with CADQuery.
**Main Features:**
- Define local coordinate systems
- Coordinate and vector transformations
- Conversion with CADQuery coordinate systems
#### [SimpleWorkplane - Workplane](simple_workplane.md)
A workplane context manager that provides a local coordinate system environment, supporting nested usage.
**Main Features:**
- Define workplanes
- Context manager support
- Nested coordinate system management
#### [TaggedMixin - Tag Mixin Class](tagged_mixin.md)
A mixin class that provides unified tag and metadata management functionality for all geometry classes.
**Main Features:**
- Tag management (add, remove, query)
- Metadata storage and retrieval
- Geometry classification and query support
### Geometry Classes
#### 0D Geometry
##### [Vertex - Vertex](vertex.md)
Represents a point in 3D space, the fundamental element of all geometries.
**Main Features:**
- Store 3D coordinates
- Tag and metadata management
- Coordinate queries
#### 1D Geometry
##### [Edge - Edge](edge.md)
Represents a 1D geometric element connecting two vertices, which can be lines, arcs, splines, etc.
**Main Features:**
- Length calculation
- Endpoint queries
- Geometry type identification
##### [Wire - Wire](wire.md)
A 1D geometric path composed of multiple connected edges, which can be open or closed.
**Main Features:**
- Edge collection management
- Closure checking
- Path analysis
#### 2D Geometry
##### [Face - Face](face.md)
Represents 2D surface geometry, enclosed by one or more wires, and can contain holes.
**Main Features:**
- Area calculation
- Normal vector queries
- Boundary wire management
##### [Shell - Shell](shell.md)
A surface collection composed of multiple faces, which can be open or closed.
**Main Features:**
- Face collection management
- Surface analysis
- Thin-walled structure support
#### 3D Geometry
##### [Solid - Solid](solid.md)
Represents a 3D closed geometry with volume, the core object of CAD modeling.
**Main Features:**
- Volume calculation
- Face and edge queries
- Automatic face tagging
- Boolean operation support
##### [Compound - Compound](compound.md)
A collection composed of multiple geometry objects, used for managing complex assemblies.
**Main Features:**
- Multi-solid management
- Hierarchy support
- Batch operations
## Class Relationship Diagram
```
TaggedMixin
├── Vertex (0D)
├── Edge (1D)
├── Wire (1D) ← composed of Edges
├── Face (2D) ← bounded by Wires
├── Shell (2D) ← collection of Faces
├── Solid (3D) ← bounded by Shells/Faces
└── Compound (3D) ← collection of Solids
CoordinateSystem ← independent utility class
SimpleWorkplane ← uses CoordinateSystem
```
## Inheritance Relationships
All geometry classes inherit from `TaggedMixin`, obtaining unified tag and metadata management functionality:
- **Tag System**: Add string tags to geometries, supporting classification and queries
- **Metadata System**: Store key-value pairs, supporting complex attribute management
- **Query Support**: Efficient querying and filtering based on tags and metadata
## Coordinate System
SimpleCAD uses a unified coordinate system:
- **Global Coordinate System**: Z-up right-handed coordinate system
- **Local Coordinate Systems**: Defined through `CoordinateSystem` and `SimpleWorkplane`
- **CADQuery Compatible**: Automatically handles conversion with CADQuery coordinate systems
## Design Principles
### Consistency
All classes follow the same design patterns and naming conventions, providing a consistent user experience.
### Extensibility
Through the tag and metadata system, users can add custom information to geometries, supporting complex application scenarios.
### Interoperability
Seamlessly integrates with CADQuery, fully utilizing CADQuery's powerful functionality.
### Ease of Use
Provides intuitive APIs and rich examples, reducing learning costs.
## Usage Guide
### Basic Usage Flow
1. **Create Geometries**: Use `make_*` functions to create basic geometries
2. **Add Tags**: Use `add_tag()` to add identifiers to geometries
3. **Set Metadata**: Use `set_metadata()` to store attribute information
4. **Combine Operations**: Use boolean operations, transformations, etc. to create complex geometries
5. **Query and Filter**: Query desired geometries based on tags and metadata
### Best Practices
1. **Tag Naming**: Use consistent naming conventions, such as `category.subcategory.detail`
2. **Metadata Organization**: Use structured data to organize related information
3. **Coordinate System Management**: Use workplanes appropriately to simplify complex geometry creation
4. **Performance Considerations**: Avoid excessive tags and large metadata that may impact performance
## Example Code
```python
from simplecadapi import *
# 创建工作平面
with SimpleWorkplane(origin=(0, 0, 0)) as wp:
# 创建基础几何体
box = make_box_rsolid(width=5, height=3, depth=2)
# 添加标签和元数据
box.add_tag("structural")
box.add_tag("aluminum")
box.set_metadata("material", "6061-T6")
box.set_metadata("density", 2.7)
# 自动标记面
box.auto_tag_faces("box")
# 查询特定面
top_faces = [f for f in box.get_faces() if f.has_tag("top")]
```
## Extension Development
If you need to create custom geometry classes:
1. Inherit from `TaggedMixin` to get tag functionality
2. Wrap the corresponding CADQuery object
3. Implement necessary geometry query methods
4. Provide appropriate string representation methods
```python
class CustomGeometry(TaggedMixin):
def __init__(self, cq_object):
TaggedMixin.__init__(self)
self.cq_object = cq_object
def get_custom_property(self):
# 实现自定义功能
pass
```
## More Resources
- [API Reference Documentation](../api/)
- [Example Code](../../examples.py)
- [User Guide](../../README.md)
- [Declarative Constraint Layout Design Draft](declarative_constraints.md)
@@ -0,0 +1,635 @@
# Compound
## Overview
`Compound` is the compound class in the SimpleCAD API, representing a collection of multiple geometry objects. A compound can contain multiple solids, faces, edges, and other types of geometric objects, making it an important tool for handling complex assemblies and multi-body geometry. It wraps the CADQuery Compound object and adds tagging functionality.
## Class Definition
```python
class Compound(TaggedMixin):
"""复合体类,包装CADQuery的Compound,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Manage collections of multiple geometry objects
- Create complex assembly structures
- Batch process multiple geometry objects
- Organize and classify geometry objects
- Implement hierarchical geometry structures
## Constructor
### `__init__(cq_compound)`
Initializes a compound object.
**Parameters:**
- `cq_compound` (cadquery.Compound): A CADQuery compound object
**Raises:**
- `ValueError`: When the input compound object is invalid
**Example:**
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid,
union_rsolidlist
)
# 创建多个实体
box = make_box_rsolid(width=2, height=2, depth=2)
cylinder = make_cylinder_rsolid(center=(3, 0, 0), radius=1, height=2)
sphere = make_sphere_rsolid(center=(0, 3, 0), radius=1)
# 通过布尔运算可能产生复合体
# 注意:实际的复合体创建方式可能因 API 实现而异
```
## Main Properties
- `cq_compound`: The underlying CADQuery compound object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_solids()`
Get all solids that make up the compound.
**Returns:**
- `List[Solid]`: List of solid objects
**Raises:**
- `ValueError`: When solid list retrieval fails
**Example:**
```python
# 假设有一个复合体对象
# compound = ... 某个复合体
solids = compound.get_solids()
print(f"复合体包含 {len(solids)} 个实体")
for i, solid in enumerate(solids):
volume = solid.get_volume()
print(f"实体 {i}: 体积 {volume:.3f}")
```
### Tag Management Methods
Methods inherited from `TaggedMixin`:
#### `add_tag(tag)`, `has_tag(tag)`, `get_tags()`, `remove_tag(tag)`
#### `set_metadata(key, value)`, `get_metadata(key, default=None)`
Usage is similar to Vertex; see [Vertex documentation](vertex.md) for details.
## Usage Examples
### Creating and Managing Compounds
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid,
translate_shape,
rotate_shape
)
def create_compound_assembly():
"""创建复合体装配"""
# 创建基础零件
parts = []
# 主体零件
main_body = make_box_rsolid(width=8, height=6, depth=4)
main_body.add_tag("main_body")
main_body.add_tag("structural")
main_body.set_metadata("part_id", "MB001")
main_body.set_metadata("material", "steel")
parts.append(main_body)
# 圆柱形零件
for i in range(3):
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=2)
cylinder = translate_shape(cylinder, offset=(2 + i*2, 1, 4))
cylinder.add_tag(f"cylinder_{i}")
cylinder.add_tag("fastener")
cylinder.set_metadata("part_id", f"CY{i:03d}")
cylinder.set_metadata("material", "brass")
parts.append(cylinder)
# 球形零件
for i in range(2):
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=0.8)
sphere = translate_shape(sphere, offset=(1 + i*6, 5, 2))
sphere.add_tag(f"sphere_{i}")
sphere.add_tag("decorative")
sphere.set_metadata("part_id", f"SP{i:03d}")
sphere.set_metadata("material", "aluminum")
parts.append(sphere)
# 分析装配体
print(f"装配体分析:")
print(f" 零件总数: {len(parts)}")
# 按类型分类
structural_parts = [p for p in parts if p.has_tag("structural")]
fastener_parts = [p for p in parts if p.has_tag("fastener")]
decorative_parts = [p for p in parts if p.has_tag("decorative")]
print(f" 结构件: {len(structural_parts)}")
print(f" 紧固件: {len(fastener_parts)}")
print(f" 装饰件: {len(decorative_parts)}")
# 按材料分类
materials = {}
for part in parts:
material = part.get_metadata("material", "unknown")
if material not in materials:
materials[material] = []
materials[material].append(part)
print(f" 材料分布:")
for material, part_list in materials.items():
total_volume = sum(p.get_volume() for p in part_list)
print(f" {material}: {len(part_list)} 件, 总体积: {total_volume:.3f}")
return parts
assembly_parts = create_compound_assembly()
```
### Hierarchical Compound Structure
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, translate_shape
def create_hierarchical_compound():
"""创建层次化复合体"""
# 创建子装配1:螺栓组件
bolt_assembly = []
# 螺栓主体
bolt_body = make_cylinder_rsolid(center=(0, 0, 0), radius=0.3, height=3)
bolt_body.add_tag("bolt_body")
bolt_body.add_tag("threaded")
bolt_body.set_metadata("assembly", "bolt_assembly")
bolt_body.set_metadata("function", "fastening")
bolt_assembly.append(bolt_body)
# 螺栓头
bolt_head = make_cylinder_rsolid(center=(0, 0, 3), radius=0.5, height=0.5)
bolt_head.add_tag("bolt_head")
bolt_head.add_tag("hex_head")
bolt_head.set_metadata("assembly", "bolt_assembly")
bolt_head.set_metadata("function", "driving")
bolt_assembly.append(bolt_head)
# 创建子装配2:支架组件
bracket_assembly = []
# 支架主体
bracket_main = make_box_rsolid(width=4, height=1, depth=2)
bracket_main.add_tag("bracket_main")
bracket_main.add_tag("mounting")
bracket_main.set_metadata("assembly", "bracket_assembly")
bracket_main.set_metadata("function", "support")
bracket_assembly.append(bracket_main)
# 支架臂
for i in range(2):
arm = make_box_rsolid(width=0.5, height=2, depth=2)
arm = translate_shape(arm, offset=(0.75 + i*2.5, 1, 0))
arm.add_tag(f"bracket_arm_{i}")
arm.add_tag("support_arm")
arm.set_metadata("assembly", "bracket_assembly")
arm.set_metadata("function", "support")
bracket_assembly.append(arm)
# 创建主装配
main_assembly = []
# 添加子装配
main_assembly.extend(bolt_assembly)
main_assembly.extend(bracket_assembly)
# 添加主体零件
main_body = make_box_rsolid(width=8, height=6, depth=4)
main_body.add_tag("main_body")
main_body.add_tag("primary_structure")
main_body.set_metadata("assembly", "main_assembly")
main_body.set_metadata("function", "housing")
main_assembly.append(main_body)
# 分析层次结构
print(f"层次化装配体分析:")
# 按装配分组
assemblies = {}
for part in main_assembly:
assembly_name = part.get_metadata("assembly", "unknown")
if assembly_name not in assemblies:
assemblies[assembly_name] = []
assemblies[assembly_name].append(part)
for assembly_name, parts in assemblies.items():
print(f" {assembly_name}:")
print(f" 零件数: {len(parts)}")
# 按功能分类
functions = {}
for part in parts:
function = part.get_metadata("function", "unknown")
if function not in functions:
functions[function] = []
functions[function].append(part)
for function, func_parts in functions.items():
total_volume = sum(p.get_volume() for p in func_parts)
print(f" {function}: {len(func_parts)} 件, 体积: {total_volume:.3f}")
return main_assembly, assemblies
main_assembly, assemblies = create_hierarchical_compound()
```
### Batch Operations on Compounds
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
translate_shape,
rotate_shape
)
def batch_operations_on_compound():
"""对复合体进行批量操作"""
# 创建一系列相似零件
parts = []
# 创建网格排列的零件
for i in range(3):
for j in range(3):
# 基础几何体
if (i + j) % 2 == 0:
part = make_box_rsolid(width=1, height=1, depth=1)
part.add_tag("box_part")
part.add_tag("cubic")
else:
part = make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=1)
part.add_tag("cylinder_part")
part.add_tag("circular")
# 定位
part = translate_shape(part, offset=(i*2, j*2, 0))
# 添加位置信息
part.add_tag(f"pos_{i}_{j}")
part.add_tag("grid_item")
part.set_metadata("grid_position", (i, j))
part.set_metadata("grid_index", i*3 + j)
parts.append(part)
# 批量分析
print(f"批量操作分析:")
print(f" 总零件数: {len(parts)}")
# 按类型统计
box_parts = [p for p in parts if p.has_tag("box_part")]
cylinder_parts = [p for p in parts if p.has_tag("cylinder_part")]
print(f" 盒子零件: {len(box_parts)}")
print(f" 圆柱零件: {len(cylinder_parts)}")
# 批量体积计算
total_volume = sum(p.get_volume() for p in parts)
box_volume = sum(p.get_volume() for p in box_parts)
cylinder_volume = sum(p.get_volume() for p in cylinder_parts)
print(f" 总体积: {total_volume:.3f}")
print(f" 盒子体积: {box_volume:.3f}")
print(f" 圆柱体积: {cylinder_volume:.3f}")
# 批量属性设置
for part in parts:
volume = part.get_volume()
grid_pos = part.get_metadata("grid_position")
# 根据体积分类
if volume < 0.5:
part.add_tag("small_part")
elif volume < 1.5:
part.add_tag("medium_part")
else:
part.add_tag("large_part")
# 根据位置分类
if grid_pos[0] == 0:
part.add_tag("left_column")
elif grid_pos[0] == 2:
part.add_tag("right_column")
else:
part.add_tag("center_column")
if grid_pos[1] == 0:
part.add_tag("bottom_row")
elif grid_pos[1] == 2:
part.add_tag("top_row")
else:
part.add_tag("center_row")
# 设置材料属性
if part.has_tag("box_part"):
part.set_metadata("material", "aluminum")
part.set_metadata("density", 2.7)
else:
part.set_metadata("material", "steel")
part.set_metadata("density", 7.8)
# 计算质量
density = part.get_metadata("density")
mass = volume * density
part.set_metadata("mass", mass)
# 批量质量分析
total_mass = sum(p.get_metadata("mass") for p in parts)
aluminum_mass = sum(p.get_metadata("mass") for p in parts if p.get_metadata("material") == "aluminum")
steel_mass = sum(p.get_metadata("mass") for p in parts if p.get_metadata("material") == "steel")
print(f" 总质量: {total_mass:.3f}")
print(f" 铝质量: {aluminum_mass:.3f}")
print(f" 钢质量: {steel_mass:.3f}")
# 位置统计
position_stats = {}
for part in parts:
pos = part.get_metadata("grid_position")
if pos not in position_stats:
position_stats[pos] = {"count": 0, "volume": 0, "mass": 0}
position_stats[pos]["count"] += 1
position_stats[pos]["volume"] += part.get_volume()
position_stats[pos]["mass"] += part.get_metadata("mass")
print(f" 位置统计:")
for pos, stats in position_stats.items():
print(f" 位置 {pos}: {stats['count']} 件, 体积: {stats['volume']:.3f}, 质量: {stats['mass']:.3f}")
return parts
batch_parts = batch_operations_on_compound()
```
### Query and Filter Compound
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
def query_and_filter_compound():
"""查询和筛选复合体"""
# 创建多样化的零件集合
parts = []
# 创建不同类型的零件
geometries = [
("small_box", make_box_rsolid(width=1, height=1, depth=1)),
("large_box", make_box_rsolid(width=3, height=2, depth=2)),
("thin_cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=0.5, height=4)),
("wide_cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=1)),
("small_sphere", make_sphere_rsolid(center=(0, 0, 0), radius=0.8)),
("large_sphere", make_sphere_rsolid(center=(0, 0, 0), radius=1.5))
]
# 为每个零件添加详细信息
for name, part in geometries:
part.add_tag(name)
# 几何类型标签
if "box" in name:
part.add_tag("rectangular")
part.add_tag("prismatic")
elif "cylinder" in name:
part.add_tag("cylindrical")
part.add_tag("rotational")
elif "sphere" in name:
part.add_tag("spherical")
part.add_tag("rotational")
# 尺寸标签
volume = part.get_volume()
if volume < 2:
part.add_tag("small")
elif volume < 10:
part.add_tag("medium")
else:
part.add_tag("large")
# 应用标签
if "thin" in name:
part.add_tag("structural")
part.set_metadata("application", "support")
elif "wide" in name:
part.add_tag("base")
part.set_metadata("application", "foundation")
else:
part.add_tag("general")
part.set_metadata("application", "multipurpose")
# 材料属性
if part.has_tag("small"):
part.set_metadata("material", "aluminum")
part.set_metadata("cost_per_unit", 2.5)
elif part.has_tag("medium"):
part.set_metadata("material", "steel")
part.set_metadata("cost_per_unit", 1.8)
else:
part.set_metadata("material", "cast_iron")
part.set_metadata("cost_per_unit", 3.2)
part.set_metadata("volume", volume)
part.set_metadata("name", name)
parts.append(part)
# 查询和筛选示例
print(f"复合体查询和筛选:")
print(f" 总零件数: {len(parts)}")
# 1. 按标签查询
print(f"\n1. 按标签查询:")
small_parts = [p for p in parts if p.has_tag("small")]
cylindrical_parts = [p for p in parts if p.has_tag("cylindrical")]
structural_parts = [p for p in parts if p.has_tag("structural")]
print(f" 小型零件: {len(small_parts)}")
print(f" 圆柱形零件: {len(cylindrical_parts)}")
print(f" 结构零件: {len(structural_parts)}")
# 2. 按体积范围查询
print(f"\n2. 按体积范围查询:")
volume_ranges = [
("超小", 0, 1),
("小", 1, 5),
("中", 5, 15),
("大", 15, float('inf'))
]
for range_name, min_vol, max_vol in volume_ranges:
range_parts = [p for p in parts if min_vol <= p.get_volume() < max_vol]
if range_parts:
print(f" {range_name}体积 ({min_vol}-{max_vol}): {len(range_parts)} 件")
# 3. 按材料查询
print(f"\n3. 按材料查询:")
materials = set(p.get_metadata("material") for p in parts)
for material in materials:
material_parts = [p for p in parts if p.get_metadata("material") == material]
total_volume = sum(p.get_volume() for p in material_parts)
total_cost = sum(p.get_metadata("cost_per_unit", 0) for p in material_parts)
print(f" {material}: {len(material_parts)} 件, 总体积: {total_volume:.3f}, 总成本: {total_cost:.2f}")
# 4. 复合查询
print(f"\n4. 复合查询:")
# 查询小型圆柱形零件
small_cylinders = [p for p in parts if p.has_tag("small") and p.has_tag("cylindrical")]
print(f" 小型圆柱形零件: {len(small_cylinders)}")
# 查询铝制零件
aluminum_parts = [p for p in parts if p.get_metadata("material") == "aluminum"]
print(f" 铝制零件: {len(aluminum_parts)}")
# 查询高成本零件
high_cost_parts = [p for p in parts if p.get_metadata("cost_per_unit", 0) > 3.0]
print(f" 高成本零件: {len(high_cost_parts)}")
# 5. 统计分析
print(f"\n5. 统计分析:")
# 按几何类型统计
geometric_types = ["rectangular", "cylindrical", "spherical"]
for geo_type in geometric_types:
type_parts = [p for p in parts if p.has_tag(geo_type)]
if type_parts:
avg_volume = sum(p.get_volume() for p in type_parts) / len(type_parts)
print(f" {geo_type}: {len(type_parts)} 件, 平均体积: {avg_volume:.3f}")
# 成本效率分析
print(f"\n6. 成本效率分析:")
for part in parts:
volume = part.get_volume()
cost = part.get_metadata("cost_per_unit", 0)
if cost > 0:
efficiency = volume / cost
part.set_metadata("volume_cost_efficiency", efficiency)
# 按效率排序
sorted_parts = sorted(parts, key=lambda p: p.get_metadata("volume_cost_efficiency", 0), reverse=True)
print(f" 最高效率零件: {sorted_parts[0].get_metadata('name')}, 效率: {sorted_parts[0].get_metadata('volume_cost_efficiency'):.3f}")
print(f" 最低效率零件: {sorted_parts[-1].get_metadata('name')}, 效率: {sorted_parts[-1].get_metadata('volume_cost_efficiency'):.3f}")
return parts
filtered_parts = query_and_filter_compound()
```
## String Representation
```python
# 假设有一个复合体对象
# compound = ... 某个复合体
compound.add_tag("assembly")
compound.set_metadata("part_count", 5)
compound.set_metadata("total_volume", 150.0)
print(compound)
```
Output:
```
Compound:
solid_count: 5
solids:
solid_0:
volume: 30.000
face_count: 6
edge_count: 12
solid_1:
volume: 25.000
face_count: 8
edge_count: 16
solid_2:
volume: 40.000
face_count: 6
edge_count: 12
solid_3:
volume: 35.000
face_count: 10
edge_count: 20
solid_4:
volume: 20.000
face_count: 4
edge_count: 8
tags: [assembly]
metadata:
part_count: 5
total_volume: 150.0
```
## Relationships with Other Geometry
- **Solid (Solid)**: Primary components of a compound
- **Face (Face)**: Indirectly associated through solids
- **Edge (Edge)**: Indirectly associated through solids and faces
## Application Scenarios
- **Assembly modeling**: Complex mechanical assemblies
- **Architectural design**: Building group modeling
- **Product design**: Multi-component products
- **Manufacturing planning**: Batch production management
- **Simulation analysis**: Multi-body system analysis
## Management Strategies
### Hierarchical Management
- Use tags and metadata to establish hierarchical structures
- Classify by function, material, process, etc.
- Enable fast querying and batch operations
### Performance Optimization
- Organize compound structures reasonably
- Avoid excessively deep nesting levels
- Optimize query and filtering algorithms
### Data Consistency
- Ensure metadata accuracy
- Maintain relationships between geometry objects
- Update statistics in a timely manner
## Notes
- Compounds may contain many geometry objects; be mindful of performance
- Modifications to geometry objects do not automatically update compound statistics
- Tag and metadata management requires establishing unified naming conventions
- Complex hierarchical structures may lead to decreased query efficiency
- Reasonable data structure design is needed to support efficient batch operations
- When performing geometric operations, consider the interrelationships between objects in the compound
@@ -0,0 +1,175 @@
# CoordinateSystem
## Overview
`CoordinateSystem` is the 3D coordinate system class in SimpleCAD API, used for defining and managing coordinate systems in 3D space. SimpleCAD uses a Z-up right-handed coordinate system with origin at (0, 0, 0), X-axis forward, Y-axis right, and Z-axis up.
## Class Definition
```python
class CoordinateSystem:
"""三维坐标系
SimpleCAD使用Z向上的右手坐标系,原点在(0, 0, 0),X轴向前,Y轴向右,Z轴向上
"""
```
## Usage
- Define local coordinate systems
- Coordinate transformation (local to global coordinates)
- Conversion with CADQuery coordinate systems
- Foundation for geometric transformations
## Constructor
### `__init__(origin, x_axis, y_axis)`
Initialize a coordinate system.
**Parameters:**
- `origin` (Tuple[float, float, float], optional): Coordinate system origin, default (0, 0, 0)
- `x_axis` (Tuple[float, float, float], optional): X-axis direction vector, default (1, 0, 0)
- `y_axis` (Tuple[float, float, float], optional): Y-axis direction vector, default (0, 1, 0)
**Exceptions:**
- `ValueError`: Raised when input coordinates or direction vectors are invalid
**Example:**
```python
from simplecadapi import CoordinateSystem
# 默认坐标系(世界坐标系)
world_cs = CoordinateSystem()
# 自定义坐标系
custom_cs = CoordinateSystem(
origin=(1, 2, 3),
x_axis=(1, 0, 0),
y_axis=(0, 1, 0)
)
# 旋转的坐标系
rotated_cs = CoordinateSystem(
origin=(0, 0, 0),
x_axis=(0.707, 0.707, 0), # 绕Z轴旋转45度
y_axis=(-0.707, 0.707, 0)
)
```
## Main Properties
- `origin`: Coordinate system origin (numpy.ndarray)
- `x_axis`: X-axis direction vector (numpy.ndarray)
- `y_axis`: Y-axis direction vector (numpy.ndarray)
- `z_axis`: Z-axis direction vector (numpy.ndarray, automatically calculated)
## Common Methods
### `transform_point(point)`
Transform local coordinates to global coordinates.
**Parameters:**
- `point` (numpy.ndarray): Local coordinate point
**Returns:**
- `numpy.ndarray`: Global coordinate point
**Example:**
```python
import numpy as np
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(1, 0, 0))
local_point = np.array([1, 0, 0])
global_point = cs.transform_point(local_point)
print(global_point) # [2. 0. 0.]
```
### `transform_vector(vector)`
Transform local direction vectors to global direction vectors (excluding translation).
**Parameters:**
- `vector` (numpy.ndarray): Local direction vector
**Returns:**
- `numpy.ndarray`: Global direction vector
**Example:**
```python
import numpy as np
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(
origin=(0, 0, 0),
x_axis=(0, 1, 0), # X轴指向Y方向
y_axis=(1, 0, 0) # Y轴指向X方向
)
local_vector = np.array([1, 0, 0]) # 局部X方向
global_vector = cs.transform_vector(local_vector)
print(global_vector) # [0. 1. 0.] (全局Y方向)
```
### `to_cq_plane()`
Convert to CADQuery's Plane object.
**Returns:**
- `cadquery.Plane`: CADQuery plane object
**Example:**
```python
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(0, 0, 1))
cq_plane = cs.to_cq_plane()
```
## Coordinate System Transformation
SimpleCAD uses Z-up coordinate system, while CADQuery uses Y-up coordinate system. The conversion rules are:
- SimpleCAD's X-axis (forward) → CADQuery's Z-axis (forward)
- SimpleCAD's Y-axis (right) → CADQuery's X-axis (right)
- SimpleCAD's Z-axis (up) → CADQuery's Y-axis (up)
## Global Coordinate System
SimpleCAD provides a global world coordinate system:
```python
from simplecadapi import WORLD_CS
print(WORLD_CS.origin) # [0. 0. 0.]
print(WORLD_CS.x_axis) # [1. 0. 0.]
print(WORLD_CS.y_axis) # [0. 1. 0.]
print(WORLD_CS.z_axis) # [0. 0. 1.]
```
## String Representation
```python
from simplecadapi import CoordinateSystem
cs = CoordinateSystem(origin=(1, 2, 3))
print(cs)
```
Output:
```
CoordinateSystem:
origin: [1.000, 2.000, 3.000]
x_axis: [1.000, 0.000, 0.000]
y_axis: [0.000, 1.000, 0.000]
z_axis: [0.000, 0.000, 1.000]
```
## Notes
- Input direction vectors are automatically normalized
- Z-axis is automatically calculated via the cross product of X-axis and Y-axis
- If a zero vector is input, a ValueError will be raised
- Coordinate systems should maintain right-handed characteristics
@@ -0,0 +1,306 @@
# Declarative Constraints Layout Design Draft
## Background and Goals
The current SimpleCADAPI is primarily imperative modeling: developers need to manually provide specific coordinates, rotation angles, and offsets. For assemblies, this approach is costly in the following scenarios:
1. Geometric relationships are stable but dimensions change frequently (repeated position recalculation after parameter changes).
2. Dependencies between multiple parts are complex (one part change cascades to affect multiple parts).
3. Need to express "relationships" rather than "values" (e.g., coaxial, fit, equidistant distribution).
Web layout (such as HTML/CSS Flexbox) has proven an effective direction:
- Users declare constraints (alignment, distribution, spacing).
- Solvers propagate constraints in the layout tree and compute final geometric values.
This proposal aims to migrate this approach to the CAD SDK:
- Retain existing imperative APIs;
- Add an optional declarative assembly layer;
- Let users describe assembly relationships, with the SDK computing each part's final pose.
## Current Implementation Status (feat/declarative-constraints-layout)
The current branch provides a runnable MVP with the following core capabilities:
- New module: `simplecadapi.constraints`
- New objects: `Assembly`, `PartHandle`, `PointAnchor`, `AxisAnchor`, `AssemblyResult`
- Supports mixed paradigm:
- First imperative pre-positioning (`translate_part` / `rotate_part`)
- Then declarative constraint solving (`coincident` / `concentric` / `offset` / `distance`)
- Supports 1D container syntax sugar: `stack(...)`
- `stack(...)` adds main axis distribution parameter: `justify=start|center|end|space-between`
- When `justify=center/end/space-between` is needed, use `bounds=(start_anchor, end_anchor)` to specify the container's main axis boundary.
Current layout implementation uses a **BBox-first** strategy:
- Alignment and distribution are primarily based on `bbox.*` anchors (AABB).
- This is consistent with Flexbox's box model thinking, but is approximate in 3D:
When parts rotate, AABB changes and layout results change accordingly.
- OBB/feature face anchors can be added later to reduce approximation errors from rotation.
- Supports assembly tree parent-child relationships and local/world transform propagation.
Framework-style constraints (functional) have been explicitly categorized into two types of mappings:
1. **Type-1 lifting mappings** (parameter space -> CAD object space)
- E.g.: `make_*_rsolid`, `make_assembly_rassembly`
2. **Type-2 algebraic transform mappings** (CAD object space -> CAD object space/result space)
- E.g.: `translate_part_rassembly`, `constrain_offset_rassembly`, `stack_rassembly`
- Solving uses `solve_assembly_rresult`, ensuring input assembly objects are not modified
Corresponding functional APIs (do not modify input) include:
- `make_assembly_rassembly`
- `clone_assembly_rassembly`
- `add_part_rassembly`
- `translate_part_rassembly` / `rotate_part_rassembly`
- `constrain_coincident_rassembly` / `constrain_concentric_rassembly`
- `constrain_offset_rassembly` / `constrain_distance_rassembly`
- `stack_rassembly`
- `solve_assembly_rresult`
Functional pipeline example:
```python
import simplecadapi as scad
asm0 = scad.make_assembly_rassembly([
("sleeve", sleeve_solid),
("rod", rod_solid),
])
asm1 = scad.translate_part_rassembly(asm0, "rod", (3.0, -2.0, 4.0))
asm2 = scad.constrain_concentric_rassembly(
asm1,
asm1.part("sleeve").axis("z"),
asm1.part("rod").axis("z"),
)
asm3 = scad.constrain_offset_rassembly(
asm2,
asm2.part("sleeve").anchor("bbox.bottom"),
asm2.part("rod").anchor("bbox.bottom"),
3.0,
axis="z",
)
result = scad.solve_assembly_rresult(asm3)
```
Example (mixed usage):
```python
import simplecadapi as scad
asm = scad.Assembly("demo")
sleeve = asm.add_part("sleeve", sleeve_solid)
rod = asm.add_part("rod", rod_solid)
# 命令式预定位
asm.translate_part("rod", (3.0, -2.0, 4.0), frame="world")
# 声明式约束
asm.concentric(sleeve.axis("z"), rod.axis("z"))
asm.offset(sleeve.anchor("bbox.bottom"), rod.anchor("bbox.bottom"), 3.0, axis="z")
result = asm.solve()
scad.export_step(result.solids(), "assembly.step")
```
## Scope
### In Scope (Phase 1)
- Assembly pose solving (rigid body 6DOF, no part topology modification).
- Basic constraint types:
- `coincident` (point/plane/axis coincidence)
- `concentric` (coaxial)
- `parallel` / `perpendicular` (directional relationships)
- `distance` (spacing)
- `offset` (offset along normal or axis direction)
- "Flex-like" 1D layout containers:
- `stack(axis="x|y|z")`
- `gap`
- `justify` (start/center/end/space-between)
- `align` (start/center/end/stretch*)
`stretch` in CAD does not perform geometric stretching; it only means aligning to the alignment baseline.
### Out of Scope (Phase 1)
- Parameter-driven topology rebuilding (e.g., automatic hole diameter changes, chamfer regeneration).
- General nonlinear symbolic solvers (CAS level).
- Complete sketch constraint system (2D sketch solver).
## Core Abstractions
### 1) Assembly Node
Each node contains:
- `name`
- `solid`
- `local frame` (node local coordinate system)
- `current transform` (variables to be solved)
- `anchors` (anchor points that can be referenced by constraints)
### 2) Anchor
Anchors are used to extract constrainable objects from geometry:
- `point`: 3D point
- `axis`: Directed line (point + direction)
- `plane`: Plane (point + normal)
- `frame`: Local coordinate system
Suggested built-in anchor sources:
- Bounding box: `bbox.min/max/center`
- Principal axes: `axis.x/y/z`
- Named faces: `face("top")`, `face("bottom")` (reuse existing tag mechanism)
### 3) Constraint
Constraints consist of:
- `type`
- `lhs anchor` / `rhs anchor`
- `value` (optional, e.g., distance)
- `priority` (hard/soft)
- `weight` (soft constraint weight)
### 4) Layout Container
Containers are constraint syntax sugar, compiled into a set of basic constraints:
- E.g., `stack([A,B,C], axis="z", gap=8)`
- `B.min_z = A.max_z + 8`
- `C.min_z = B.max_z + 8`
- Plus alignment constraints (e.g., XY centering)
## API Draft
```python
import simplecadapi as scad
from simplecadapi.constraints import Assembly, stack
asm = Assembly(name="shock_absorber")
sleeve = asm.add_part("sleeve", sleeve_solid)
rod = asm.add_part("rod", rod_solid)
spring = asm.add_part("spring", spring_solid)
asm.concentric(rod.axis("z"), sleeve.axis("z"))
asm.offset(rod.anchor("bottom"), sleeve.anchor("bottom"), 10.0)
asm.distance(rod.anchor("top"), sleeve.anchor("top"), min_value=5.0)
stack(
asm,
parts=[spring],
axis="z",
relative_to=sleeve,
align="center",
justify="start",
gap=8.0,
)
result = asm.solve()
solids = result.solids()
scad.export_step(solids, "shock_absorber_assembly.step")
```
## Solving Strategy (Layered)
### Layer A: Parsing and Normalization
- Convert constraints and container rules into residual equations.
- Map anchor references to real-time geometric query functions.
### Layer B: Graph Constraint Propagation (Fast Path)
- First perform topological solving for directly propagatable rigid constraints:
- Coaxial + offset + alignment can directly derive partial poses.
- Build dependency graph and perform incremental updates (dirty propagation).
### Layer C: Numerical Solving (Fallback)
- Use least squares solving for remaining degrees of freedom (hard constraints have highest priority).
- For over-constrained or contradictory constraints, output diagnostic reports:
- Conflicting constraint pairs
- Constraints with highest residuals
- Recommended relaxation items (downgrade from hard to soft)
## Result Model
`solve()` returns an object that should contain:
- `transforms`: Final pose for each part
- `solids()`: List of solids with poses applied
- `report`: Solving information
- Whether converged
- Number of iterations
- Maximum residual
- Conflict/over-constraint description
## Compatibility Strategy with Existing API
1. No changes to existing `operations.py` imperative interfaces.
2. New independent module (suggested `simplecadapi.constraints`).
3. Export still reuses `export_step` / `export_stl`, can directly export `result.solids()`.
## Milestone Plan
### M0 - Design and Feasibility Verification (Current Phase)
- Output this design document.
- Define minimum API surface.
- Select first batch of constraint types and diagnostic formats.
### M1 - Minimum Viable Prototype (MVP)
- `Assembly.add_part()`
- Anchors: `bbox` + `axis` + `face tag`
- Constraints: `concentric` + `offset` + `distance`
- Solving: Support single-chain assemblies (no loops)
### M2 - Flex-like Layout Containers
- `stack(axis, gap, align, justify)`
- Compile container rules into constraints
- Support simple incremental updates
### M3 - Diagnostics and Engineering
- Conflict localization and interpretable error messages
- Solving logs and visual output (text reports)
- Unit test coverage for typical assembly scenarios
### M4 - Advanced Capabilities
- Soft constraint weight system
- Complex constraint loops
- Performance optimization (caching, partitioned solving)
## Testing Recommendations
At least cover the following scenarios:
1. **Basic convergence**: Coaxial + offset + alignment, unique result.
2. **Under-constrained**: Clear warning when degrees of freedom are not locked.
3. **Over-constrained**: Conflict diagnostics when constraints are contradictory.
4. **Incremental updates**: Modifying a single parameter triggers only local recalculation.
5. **Export consistency**: `result.solids()` can directly export STEP/STL.
## Risks and Mitigations
- **Risk:** Constraint semantics too abstract, making the API hard to use.
**Mitigation:** Start with high-frequency assembly scenarios, providing only a few strongly semantic constraints.
- **Risk:** Numerical solving is unstable.
**Mitigation:** First do graph propagation fast path; numerical solving only handles remaining degrees of freedom.
- **Risk:** Conflict with existing user code.
**Mitigation:** New module isolation, disabled by default, strictly maintain backward compatibility.
## Conclusion
Migrating the Flexbox-style "declare relationships -> solve geometry" paradigm to the CAD SDK is feasible and can significantly improve assembly modeling efficiency and maintainability. It is recommended to use "assembly pose constraints + 1D container layout" as the Phase 1 entry point, deliver an MVP first, and then gradually enhance solving capabilities and diagnostic experience.
@@ -0,0 +1,389 @@
# Edge
## Overview
`Edge` is the edge class in SimpleCAD API, representing a 1D geometric element connecting two vertices. Edges can be lines, arcs, splines, and other types of curves. It wraps CADQuery's Edge object and adds tag functionality.
## Class Definition
```python
class Edge(TaggedMixin):
"""边类,包装CADQuery的Edge,添加标签功能"""
```
## Inheritance Relationships
- Inherits from `TaggedMixin`, with tag and metadata functionality
## Usage
- Represent connections between two points
- Fundamental elements composing Wires and Faces
- Provide geometric information (length, vertices, etc.)
- Support tag management and queries
## Constructor
### `__init__(cq_edge)`
Initialize an edge object.
**Parameters:**
- `cq_edge` (cadquery.Edge): CADQuery edge object
**Exceptions:**
- `ValueError`: Raised when the input edge object is invalid
**Example:**
```python
from simplecadapi import make_line_redge, make_circle_redge
# 通过 SimpleCAD 函数创建边
line_edge = make_line_redge(start=(0, 0, 0), end=(1, 1, 0))
circle_edge = make_circle_redge(center=(0, 0, 0), radius=1.0)
```
## Main Properties
- `cq_edge`: Underlying CADQuery edge object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_length()`
Get the length of the edge.
**Returns:**
- `float`: Edge length
**Exceptions:**
- `ValueError`: Raised when length retrieval fails
**Example:**
```python
from simplecadapi import make_line_redge, make_circle_redge
import math
# 直线边
line = make_line_redge(start=(0, 0, 0), end=(3, 4, 0))
line_length = line.get_length()
print(f"直线长度: {line_length}") # 5.0
# 圆形边
circle = make_circle_redge(center=(0, 0, 0), radius=2.0)
circle_length = circle.get_length()
print(f"圆形周长: {circle_length}") # 约 12.566 (2π * 2)
```
### `get_start_vertex()`
Get the start vertex of the edge.
**Returns:**
- `Vertex`: Start vertex object
**Exceptions:**
- `ValueError`: Raised when vertex retrieval fails
**Example:**
```python
from simplecadapi import make_line_redge
line = make_line_redge(start=(1, 2, 3), end=(4, 5, 6))
start_vertex = line.get_start_vertex()
start_coords = start_vertex.get_coordinates()
print(f"起始点坐标: {start_coords}") # (1.0, 2.0, 3.0)
```
### `get_end_vertex()`
Get the end vertex of the edge.
**Returns:**
- `Vertex`: End vertex object
**Exceptions:**
- `ValueError`: Raised when vertex retrieval fails
**Example:**
```python
from simplecadapi import make_line_redge
line = make_line_redge(start=(1, 2, 3), end=(4, 5, 6))
end_vertex = line.get_end_vertex()
end_coords = end_vertex.get_coordinates()
print(f"结束点坐标: {end_coords}") # (4.0, 5.0, 6.0)
```
### 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 Different Types of Edges
```python
from simplecadapi import (
make_line_redge,
make_circle_redge,
make_three_point_arc_redge,
make_spline_redge
)
# 直线边
line = make_line_redge(start=(0, 0, 0), end=(5, 0, 0))
line.add_tag("base_line")
# 圆形边
circle = make_circle_redge(center=(0, 0, 0), radius=2.0)
circle.add_tag("full_circle")
# 三点圆弧边
arc = make_three_point_arc_redge(
start=(0, 0, 0),
mid=(1, 1, 0),
end=(2, 0, 0)
)
arc.add_tag("arc_segment")
# 样条边
spline = make_spline_redge(points=[(0, 0, 0), (1, 1, 0), (2, 0, 0), (3, 1, 0)])
spline.add_tag("smooth_curve")
# 打印边的信息
edges = [line, circle, arc, spline]
for edge in edges:
print(f"边标签: {edge.get_tags()}, 长度: {edge.get_length():.3f}")
```
### Edge Analysis and Classification
```python
from simplecadapi import make_line_redge
import math
def analyze_edge_collection():
"""分析边的集合"""
# 创建多条边
edges = [
make_line_redge(start=(0, 0, 0), end=(1, 0, 0)), # 水平线
make_line_redge(start=(0, 0, 0), end=(0, 1, 0)), # 垂直线
make_line_redge(start=(0, 0, 0), end=(1, 1, 0)), # 对角线
make_line_redge(start=(0, 0, 0), end=(2, 0, 0)), # 长水平线
make_line_redge(start=(0, 0, 0), end=(0, 2, 0)), # 长垂直线
]
# 分析每条边
for i, edge in enumerate(edges):
length = edge.get_length()
start_coords = edge.get_start_vertex().get_coordinates()
end_coords = edge.get_end_vertex().get_coordinates()
# 计算方向向量
direction = (
end_coords[0] - start_coords[0],
end_coords[1] - start_coords[1],
end_coords[2] - start_coords[2]
)
# 分类边
if abs(direction[0]) > 0 and abs(direction[1]) == 0:
edge.add_tag("horizontal")
elif abs(direction[0]) == 0 and abs(direction[1]) > 0:
edge.add_tag("vertical")
elif abs(direction[0]) > 0 and abs(direction[1]) > 0:
edge.add_tag("diagonal")
# 根据长度分类
if length < 1.5:
edge.add_tag("short")
else:
edge.add_tag("long")
# 添加元数据
edge.set_metadata("length", length)
edge.set_metadata("direction", direction)
edge.set_metadata("index", i)
print(f"边 {i}: 长度={length:.3f}, 标签={edge.get_tags()}")
analyze_edge_collection()
```
### Building Edge Networks
```python
from simplecadapi import make_line_redge
def create_edge_network():
"""创建边的网络结构"""
# 定义节点
nodes = [
(0, 0, 0), # A
(2, 0, 0), # B
(2, 2, 0), # C
(0, 2, 0), # D
(1, 1, 0), # E (中心点)
]
# 定义连接关系
connections = [
(0, 1), # A-B
(1, 2), # B-C
(2, 3), # C-D
(3, 0), # D-A
(0, 4), # A-E
(1, 4), # B-E
(2, 4), # C-E
(3, 4), # D-E
]
edges = []
for i, (start_idx, end_idx) in enumerate(connections):
start_point = nodes[start_idx]
end_point = nodes[end_idx]
edge = make_line_redge(start=start_point, end=end_point)
# 添加连接信息
edge.add_tag(f"connection_{chr(65+start_idx)}{chr(65+end_idx)}")
# 分类边
if start_idx < 4 and end_idx < 4:
edge.add_tag("perimeter")
else:
edge.add_tag("internal")
# 添加元数据
edge.set_metadata("start_node", chr(65+start_idx))
edge.set_metadata("end_node", chr(65+end_idx))
edge.set_metadata("connection_index", i)
edges.append(edge)
return edges
# 创建网络
network_edges = create_edge_network()
# 分析网络
perimeter_edges = [e for e in network_edges if e.has_tag("perimeter")]
internal_edges = [e for e in network_edges if e.has_tag("internal")]
print(f"周边边数: {len(perimeter_edges)}")
print(f"内部边数: {len(internal_edges)}")
# 计算总长度
total_length = sum(edge.get_length() for edge in network_edges)
print(f"网络总长度: {total_length:.3f}")
```
### Edge Geometric Calculations
```python
from simplecadapi import make_line_redge, make_circle_redge
import math
def calculate_edge_properties():
"""计算边的几何属性"""
# 创建不同类型的边
line = make_line_redge(start=(0, 0, 0), end=(3, 4, 0))
circle = make_circle_redge(center=(0, 0, 0), radius=5.0)
# 直线属性
line_length = line.get_length()
line_start = line.get_start_vertex().get_coordinates()
line_end = line.get_end_vertex().get_coordinates()
# 计算直线的中点
line_midpoint = (
(line_start[0] + line_end[0]) / 2,
(line_start[1] + line_end[1]) / 2,
(line_start[2] + line_end[2]) / 2
)
# 计算直线的方向向量
line_direction = (
line_end[0] - line_start[0],
line_end[1] - line_start[1],
line_end[2] - line_start[2]
)
# 归一化方向向量
line_dir_length = math.sqrt(sum(x*x for x in line_direction))
line_unit_direction = tuple(x / line_dir_length for x in line_direction)
# 圆形属性
circle_length = circle.get_length() # 周长
circle_radius = circle_length / (2 * math.pi)
# 存储计算结果
line.set_metadata("midpoint", line_midpoint)
line.set_metadata("direction", line_direction)
line.set_metadata("unit_direction", line_unit_direction)
line.add_tag("calculated")
circle.set_metadata("radius", circle_radius)
circle.set_metadata("circumference", circle_length)
circle.add_tag("calculated")
print(f"直线长度: {line_length:.3f}")
print(f"直线中点: {line_midpoint}")
print(f"直线单位方向: {line_unit_direction}")
print(f"圆形周长: {circle_length:.3f}")
print(f"圆形半径: {circle_radius:.3f}")
calculate_edge_properties()
```
## String Representation
```python
from simplecadapi import make_line_redge
edge = make_line_redge(start=(0, 0, 0), end=(3, 4, 0))
edge.add_tag("example_edge")
edge.set_metadata("type", "line")
print(edge)
```
Output:
```
Edge:
length: 5.000
vertices:
start: (0.0, 0.0, 0.0)
end: (3.0, 4.0, 0.0)
tags: [example_edge]
metadata:
type: line
```
## Relationships with Other Geometries
- **Vertex**: Endpoints of edges
- **Wire**: Composed of multiple connected edges
- **Face**: Boundary defined by edges (via wires)
- **Solid**: Ultimately composed of faces formed by edges
## Notes
- Edge length is determined by its geometry and cannot be directly modified
- Circular edges are complete circles with identical start and end vertices
- Spline edge lengths are approximate values and may have precision errors
- Edge directionality may affect certain operations
- Tags and metadata do not affect edge geometry properties
- When retrieving vertices, for closed edges like circular edges, start and end vertices may be identical
@@ -0,0 +1,550 @@
# Face
## Overview
`Face` is the face class in the SimpleCAD API, representing 2D surface geometry. A face is bounded by one or more wires, including an outer boundary and possibly inner boundaries (holes). It wraps the CADQuery Face object and adds tagging functionality.
## Class Definition
```python
class Face(TaggedMixin):
"""面类,包装CADQuery的Face,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Represent 2D surface areas
- Form the boundary of solids (Solid)
- Define cross-sections for sweep, extrude, and other operations
- Calculate geometric properties such as area and normal vectors
## Constructor
### `__init__(cq_face)`
Initializes a face object.
**Parameters:**
- `cq_face` (cadquery.Face): A CADQuery face object
**Raises:**
- `ValueError`: When the input face object is invalid
**Example:**
```python
from simplecadapi import (
make_rectangle_rface,
make_circle_rface,
make_face_from_wire_rface,
make_rectangle_rwire
)
# 通过 SimpleCAD 函数创建面
rectangle = make_rectangle_rface(width=5, height=3)
circle = make_circle_rface(center=(0, 0, 0), radius=2.0)
# 从线创建面
wire = make_rectangle_rwire(width=4, height=4)
face_from_wire = make_face_from_wire_rface(wire)
```
## Main Properties
- `cq_face`: The underlying CADQuery face object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_area()`
Get the area of the face.
**Returns:**
- `float`: The area of the face
**Raises:**
- `ValueError`: When area retrieval fails
**Example:**
```python
from simplecadapi import make_rectangle_rface, make_circle_rface
import math
# 矩形面
rectangle = make_rectangle_rface(width=5, height=3)
rect_area = rectangle.get_area()
print(f"矩形面积: {rect_area}") # 15.0
# 圆形面
circle = make_circle_rface(center=(0, 0, 0), radius=2.0)
circle_area = circle.get_area()
expected_area = math.pi * 2.0 * 2.0
print(f"圆形面积: {circle_area:.3f}, 期望: {expected_area:.3f}")
```
### `get_normal_at(u, v)`
Get the normal vector of the face at the specified parameter position.
**Parameters:**
- `u` (float, optional): U parameter, default 0.5
- `v` (float, optional): V parameter, default 0.5
**Returns:**
- `cadquery.Vector`: Normal vector
**Raises:**
- `ValueError`: When normal vector retrieval fails
**Example:**
```python
from simplecadapi import make_rectangle_rface
rectangle = make_rectangle_rface(width=5, height=3)
normal = rectangle.get_normal_at()
print(f"法向量: ({normal.x:.3f}, {normal.y:.3f}, {normal.z:.3f})")
```
### `get_outer_wire()`
Get the outer boundary wire of the face.
**Returns:**
- `Wire`: Outer boundary wire object
**Raises:**
- `ValueError`: When outer boundary wire retrieval fails
**Example:**
```python
from simplecadapi import make_rectangle_rface
rectangle = make_rectangle_rface(width=5, height=3)
outer_wire = rectangle.get_outer_wire()
edges = outer_wire.get_edges()
print(f"外边界由 {len(edges)} 条边组成")
```
### 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 Different Types of Faces
```python
from simplecadapi import (
make_rectangle_rface,
make_circle_rface,
make_face_from_wire_rface,
make_polyline_rwire
)
# 矩形面
rectangle = make_rectangle_rface(width=10, height=6)
rectangle.add_tag("rectangle")
rectangle.add_tag("quadrilateral")
# 圆形面
circle = make_circle_rface(center=(0, 0, 0), radius=3.0)
circle.add_tag("circle")
circle.add_tag("curved")
# 复杂多边形面
points = [
(0, 0, 0), (4, 0, 0), (4, 3, 0), (2, 5, 0), (0, 3, 0), (0, 0, 0)
]
polygon_wire = make_polyline_rwire(points=points)
polygon = make_face_from_wire_rface(polygon_wire)
polygon.add_tag("polygon")
polygon.add_tag("complex")
# 分析面的属性
faces = [rectangle, circle, polygon]
for face in faces:
area = face.get_area()
normal = face.get_normal_at()
outer_wire = face.get_outer_wire()
edges = outer_wire.get_edges()
tags = face.get_tags()
print(f"面类型: {tags}")
print(f" 面积: {area:.3f}")
print(f" 法向量: ({normal.x:.3f}, {normal.y:.3f}, {normal.z:.3f})")
print(f" 边数: {len(edges)}")
print()
```
### Geometric Analysis of Faces
```python
from simplecadapi import make_rectangle_rface, make_circle_rface
import math
def analyze_face_geometry():
"""分析面的几何属性"""
# 创建不同尺寸的矩形
rectangles = [
make_rectangle_rface(width=2, height=3),
make_rectangle_rface(width=4, height=4),
make_rectangle_rface(width=6, height=2)
]
# 创建不同半径的圆
circles = [
make_circle_rface(center=(0, 0, 0), radius=1.0),
make_circle_rface(center=(0, 0, 0), radius=2.0),
make_circle_rface(center=(0, 0, 0), radius=3.0)
]
# 分析矩形
for i, rect in enumerate(rectangles):
area = rect.get_area()
outer_wire = rect.get_outer_wire()
edges = outer_wire.get_edges()
# 计算周长
perimeter = sum(edge.get_length() for edge in edges)
# 计算长宽比
lengths = [edge.get_length() for edge in edges]
lengths.sort()
aspect_ratio = lengths[1] / lengths[0] if lengths[0] > 0 else 1.0
rect.add_tag(f"rectangle_{i}")
rect.set_metadata("area", area)
rect.set_metadata("perimeter", perimeter)
rect.set_metadata("aspect_ratio", aspect_ratio)
if aspect_ratio == 1.0:
rect.add_tag("square")
elif aspect_ratio > 2.0:
rect.add_tag("elongated")
print(f"矩形 {i}: 面积={area:.3f}, 周长={perimeter:.3f}, 长宽比={aspect_ratio:.3f}")
# 分析圆形
for i, circle in enumerate(circles):
area = circle.get_area()
outer_wire = circle.get_outer_wire()
edges = outer_wire.get_edges()
# 计算周长(圆周长)
perimeter = sum(edge.get_length() for edge in edges)
# 从面积计算半径
radius_from_area = math.sqrt(area / math.pi)
# 从周长计算半径
radius_from_perimeter = perimeter / (2 * math.pi)
circle.add_tag(f"circle_{i}")
circle.set_metadata("area", area)
circle.set_metadata("perimeter", perimeter)
circle.set_metadata("radius_from_area", radius_from_area)
circle.set_metadata("radius_from_perimeter", radius_from_perimeter)
if radius_from_area < 1.5:
circle.add_tag("small")
elif radius_from_area > 2.5:
circle.add_tag("large")
else:
circle.add_tag("medium")
print(f"圆形 {i}: 面积={area:.3f}, 周长={perimeter:.3f}, 半径={radius_from_area:.3f}")
analyze_face_geometry()
```
### Faces with Holes
```python
from simplecadapi import (
make_rectangle_rface,
make_circle_rface,
make_face_from_wire_rface,
make_rectangle_rwire,
make_circle_rwire
)
def create_face_with_holes():
"""创建带孔的面(概念示例)"""
# 创建外边界
outer_boundary = make_rectangle_rwire(width=10, height=8)
# 创建内边界(孔)
hole1 = make_circle_rwire(center=(3, 2, 0), radius=1.0)
hole2 = make_circle_rwire(center=(7, 6, 0), radius=1.5)
# 注意:SimpleCAD 当前版本可能不直接支持多边界面
# 这里展示概念和标签使用
# 主面
main_face = make_rectangle_rface(width=10, height=8)
main_face.add_tag("main_surface")
main_face.add_tag("with_holes")
# 孔面(用于布尔运算)
hole_face1 = make_circle_rface(center=(3, 2, 0), radius=1.0)
hole_face1.add_tag("hole")
hole_face1.add_tag("circular")
hole_face1.set_metadata("hole_id", 1)
hole_face1.set_metadata("center", (3, 2, 0))
hole_face1.set_metadata("radius", 1.0)
hole_face2 = make_circle_rface(center=(7, 6, 0), radius=1.5)
hole_face2.add_tag("hole")
hole_face2.add_tag("circular")
hole_face2.set_metadata("hole_id", 2)
hole_face2.set_metadata("center", (7, 6, 0))
hole_face2.set_metadata("radius", 1.5)
# 计算有效面积
main_area = main_face.get_area()
hole1_area = hole_face1.get_area()
hole2_area = hole_face2.get_area()
effective_area = main_area - hole1_area - hole2_area
main_face.set_metadata("total_area", main_area)
main_face.set_metadata("hole_area", hole1_area + hole2_area)
main_face.set_metadata("effective_area", effective_area)
print(f"主面面积: {main_area:.3f}")
print(f"孔面积总和: {hole1_area + hole2_area:.3f}")
print(f"有效面积: {effective_area:.3f}")
return main_face, [hole_face1, hole_face2]
main_face, holes = create_face_with_holes()
```
### Face Transformation and Operations
```python
from simplecadapi import (
make_rectangle_rface,
translate_shape,
rotate_shape
)
def transform_faces():
"""变换面的操作"""
# 创建基础面
base_face = make_rectangle_rface(width=4, height=3)
base_face.add_tag("base")
base_face.add_tag("original")
# 应用变换
translated_face = translate_shape(base_face, offset=(6, 0, 0))
translated_face.add_tag("translated")
rotated_face = rotate_shape(base_face, axis=(0, 0, 1), angle=45)
rotated_face.add_tag("rotated")
elevated_face = translate_shape(base_face, offset=(0, 0, 2))
elevated_face.add_tag("elevated")
# 收集所有面
all_faces = [base_face, translated_face, rotated_face, elevated_face]
# 分析变换结果
for face in all_faces:
area = face.get_area()
normal = face.get_normal_at()
outer_wire = face.get_outer_wire()
edges = outer_wire.get_edges()
# 计算边界框
all_coords = []
for edge in edges:
start_coords = edge.get_start_vertex().get_coordinates()
end_coords = edge.get_end_vertex().get_coordinates()
all_coords.extend([start_coords, end_coords])
if all_coords:
min_x = min(coord[0] for coord in all_coords)
max_x = max(coord[0] for coord in all_coords)
min_y = min(coord[1] for coord in all_coords)
max_y = max(coord[1] for coord in all_coords)
min_z = min(coord[2] for coord in all_coords)
max_z = max(coord[2] for coord in all_coords)
face.set_metadata("bbox_min", (min_x, min_y, min_z))
face.set_metadata("bbox_max", (max_x, max_y, max_z))
face.set_metadata("area", area)
face.set_metadata("normal", (normal.x, normal.y, normal.z))
print(f"面标签: {face.get_tags()}")
print(f" 面积: {area:.3f}")
print(f" 法向量: ({normal.x:.3f}, {normal.y:.3f}, {normal.z:.3f})")
if face.get_metadata("bbox_min"):
print(f" 边界框: {face.get_metadata('bbox_min')} 到 {face.get_metadata('bbox_max')}")
print()
transform_faces()
```
### Face Classification and Filtering
```python
from simplecadapi import make_rectangle_rface, make_circle_rface
def classify_faces():
"""分类和筛选面"""
# 创建不同类型的面
faces = []
# 小矩形
small_rects = [
make_rectangle_rface(width=1, height=1),
make_rectangle_rface(width=2, height=1),
make_rectangle_rface(width=1, height=2)
]
# 大矩形
large_rects = [
make_rectangle_rface(width=5, height=4),
make_rectangle_rface(width=6, height=3),
make_rectangle_rface(width=4, height=6)
]
# 圆形
circles = [
make_circle_rface(center=(0, 0, 0), radius=1.0),
make_circle_rface(center=(0, 0, 0), radius=2.0),
make_circle_rface(center=(0, 0, 0), radius=3.0)
]
# 标记面
for i, face in enumerate(small_rects):
face.add_tag("rectangle")
face.add_tag("small")
face.set_metadata("size_category", "small")
face.set_metadata("shape_type", "rectangle")
faces.append(face)
for i, face in enumerate(large_rects):
face.add_tag("rectangle")
face.add_tag("large")
face.set_metadata("size_category", "large")
face.set_metadata("shape_type", "rectangle")
faces.append(face)
for i, face in enumerate(circles):
face.add_tag("circle")
area = face.get_area()
if area < 10:
face.add_tag("small")
face.set_metadata("size_category", "small")
elif area > 20:
face.add_tag("large")
face.set_metadata("size_category", "large")
else:
face.add_tag("medium")
face.set_metadata("size_category", "medium")
face.set_metadata("shape_type", "circle")
faces.append(face)
# 分类统计
rectangles = [f for f in faces if f.has_tag("rectangle")]
circles = [f for f in faces if f.has_tag("circle")]
small_faces = [f for f in faces if f.has_tag("small")]
large_faces = [f for f in faces if f.has_tag("large")]
print(f"总面数: {len(faces)}")
print(f"矩形面: {len(rectangles)}")
print(f"圆形面: {len(circles)}")
print(f"小面: {len(small_faces)}")
print(f"大面: {len(large_faces)}")
# 计算统计信息
total_area = sum(f.get_area() for f in faces)
avg_area = total_area / len(faces)
print(f"总面积: {total_area:.3f}")
print(f"平均面积: {avg_area:.3f}")
return faces
classified_faces = classify_faces()
```
## String Representation
```python
from simplecadapi import make_rectangle_rface
face = make_rectangle_rface(width=5, height=3)
face.add_tag("example_face")
face.set_metadata("material", "steel")
print(face)
```
Output:
```
Face:
area: 15.000
normal: [0.000, 0.000, 1.000]
outer_wire:
Wire:
edge_count: 4
closed: True
edges:
edge_0:
length: 5.000
vertices:
start: (0.0, 0.0, 0.0)
end: (5.0, 0.0, 0.0)
edge_1:
length: 3.000
vertices:
start: (5.0, 0.0, 0.0)
end: (5.0, 3.0, 0.0)
edge_2:
length: 5.000
vertices:
start: (5.0, 3.0, 0.0)
end: (0.0, 3.0, 0.0)
edge_3:
length: 3.000
vertices:
start: (0.0, 3.0, 0.0)
end: (0.0, 0.0, 0.0)
tags: [example_face]
metadata:
material: steel
```
## Relationships with Other Geometry
- **Wire (Wire)**: Boundary of the face
- **Edge (Edge)**: Indirectly associated through wires
- **Solid (Solid)**: Faces form the surfaces of a solid
- **Shell (Shell)**: A collection of surfaces composed of multiple faces
## Notes
- Faces must be closed, bounded by closed wires
- The face normal direction follows the right-hand rule
- Area calculation includes all regions bounded by the boundary
- Faces with holes require special treatment (outer boundary + inner boundary)
- Face orientation affects subsequent solid operations
- Complex faces may have self-intersection or degenerate cases
- The u, v parameter range is typically [0, 1]
@@ -0,0 +1,518 @@
# Shell
## Overview
`Shell` is the shell class in the SimpleCAD API, representing an open or closed surface collection composed of multiple faces. A shell is a geometry type between faces and solids, useful for creating complex surface structures. It wraps the CADQuery Shell object and adds tagging functionality.
## Class Definition
```python
class Shell(TaggedMixin):
"""壳类,包装CADQuery的Shell,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Represent complex surface structures
- Serve as an intermediate state of solids
- Create open surface geometry
- Represent thin-walled structures
## Constructor
### `__init__(cq_shell)`
Initializes a shell object.
**Parameters:**
- `cq_shell` (Union[cadquery.Shell, Any]): A CADQuery shell object or other Shape object
**Raises:**
- `ValueError`: When the input shell object is invalid
**Example:**
```python
from simplecadapi import make_box_rsolid, shell_rsolid
# 创建一个实体,然后制作壳
box = make_box_rsolid(width=5, height=3, depth=2)
# 选择要移除的面创建壳
shell = shell_rsolid(box, thickness=0.2) # 创建薄壁壳
```
## Main Properties
- `cq_shell`: The underlying CADQuery shell object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_faces()`
Get all faces that make up the shell.
**Returns:**
- `List[Face]`: List of face objects
**Raises:**
- `ValueError`: When face list retrieval fails
**Example:**
```python
from simplecadapi import make_box_rsolid, shell_rsolid
# 创建壳
box = make_box_rsolid(width=4, height=3, depth=2)
shell = shell_rsolid(box, thickness=0.1)
# 获取面
faces = shell.get_faces()
print(f"壳由 {len(faces)} 个面组成")
for i, face in enumerate(faces):
area = face.get_area()
print(f"面 {i}: 面积 {area:.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 Analyzing Shells
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, shell_rsolid
def create_and_analyze_shells():
"""创建和分析不同类型的壳"""
# 创建盒子壳
box = make_box_rsolid(width=6, height=4, depth=3)
box_shell = shell_rsolid(box, thickness=0.2)
box_shell.add_tag("box_shell")
box_shell.add_tag("rectangular")
# 创建圆柱壳
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=5)
cylinder_shell = shell_rsolid(cylinder, thickness=0.15)
cylinder_shell.add_tag("cylinder_shell")
cylinder_shell.add_tag("cylindrical")
# 分析壳
shells = [box_shell, cylinder_shell]
for shell in shells:
faces = shell.get_faces()
total_area = sum(face.get_area() for face in faces)
# 分析面的类型
face_areas = [face.get_area() for face in faces]
min_area = min(face_areas)
max_area = max(face_areas)
avg_area = sum(face_areas) / len(face_areas)
shell.set_metadata("face_count", len(faces))
shell.set_metadata("total_surface_area", total_area)
shell.set_metadata("min_face_area", min_area)
shell.set_metadata("max_face_area", max_area)
shell.set_metadata("avg_face_area", avg_area)
# 标记面的大小
for i, face in enumerate(faces):
area = face.get_area()
face.add_tag(f"shell_face_{i}")
if area < avg_area * 0.5:
face.add_tag("small_face")
elif area > avg_area * 1.5:
face.add_tag("large_face")
else:
face.add_tag("medium_face")
print(f"壳类型: {shell.get_tags()}")
print(f" 面数: {len(faces)}")
print(f" 总表面积: {total_area:.3f}")
print(f" 平均面积: {avg_area:.3f}")
print(f" 面积范围: {min_area:.3f} - {max_area:.3f}")
print()
create_and_analyze_shells()
```
### Creating Complex Shells
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
union_rsolidlist,
shell_rsolid
)
def create_complex_shell():
"""创建复杂的壳结构"""
# 创建复合几何体
main_body = make_box_rsolid(width=8, height=6, depth=4)
main_body.add_tag("main_body")
# 添加圆柱形突出部分
cylinder1 = make_cylinder_rsolid(center=(2, 1, 4), radius=1, height=2)
cylinder1.add_tag("protrusion_1")
cylinder2 = make_cylinder_rsolid(center=(6, 5, 4), radius=0.8, height=1.5)
cylinder2.add_tag("protrusion_2")
# 合并几何体
combined = union_rsolidlist([main_body, cylinder1])[0]
combined = union_rsolidlist([combined, cylinder2])[0]
combined.add_tag("combined_solid")
# 创建壳
complex_shell = shell_rsolid(combined, thickness=0.3)
complex_shell.add_tag("complex_shell")
complex_shell.add_tag("multi_feature")
# 分析复杂壳
faces = complex_shell.get_faces()
# 按面积分类
large_faces = []
medium_faces = []
small_faces = []
total_area = sum(face.get_area() for face in faces)
avg_area = total_area / len(faces)
for i, face in enumerate(faces):
area = face.get_area()
face.add_tag(f"complex_face_{i}")
face.set_metadata("area", area)
face.set_metadata("area_ratio", area / avg_area)
if area > avg_area * 2:
large_faces.append(face)
face.add_tag("large")
elif area > avg_area * 0.5:
medium_faces.append(face)
face.add_tag("medium")
else:
small_faces.append(face)
face.add_tag("small")
complex_shell.set_metadata("large_face_count", len(large_faces))
complex_shell.set_metadata("medium_face_count", len(medium_faces))
complex_shell.set_metadata("small_face_count", len(small_faces))
complex_shell.set_metadata("total_surface_area", total_area)
print(f"复杂壳分析:")
print(f" 总面数: {len(faces)}")
print(f" 大面数: {len(large_faces)}")
print(f" 中面数: {len(medium_faces)}")
print(f" 小面数: {len(small_faces)}")
print(f" 总表面积: {total_area:.3f}")
return complex_shell
complex_shell = create_complex_shell()
```
### Shell Quality Check
```python
from simplecadapi import make_box_rsolid, shell_rsolid
def check_shell_quality():
"""检查壳的质量"""
# 创建测试壳
test_solids = [
make_box_rsolid(width=5, height=5, depth=5), # 立方体
make_box_rsolid(width=10, height=2, depth=1), # 薄板
make_box_rsolid(width=1, height=1, depth=10) # 长条
]
test_names = ["cube", "plate", "rod"]
for i, solid in enumerate(test_solids):
name = test_names[i]
solid.add_tag(name)
# 创建不同厚度的壳
thicknesses = [0.1, 0.2, 0.5]
for thickness in thicknesses:
try:
shell = shell_rsolid(solid, thickness=thickness)
shell.add_tag(f"{name}_shell")
shell.add_tag(f"thickness_{thickness}")
faces = shell.get_faces()
# 检查面的连续性
edge_count = 0
total_area = 0
for face in faces:
area = face.get_area()
total_area += area
outer_wire = face.get_outer_wire()
edges = outer_wire.get_edges()
edge_count += len(edges)
# 检查面的有效性
if area < 1e-6:
face.add_tag("degenerate")
elif area > 1000:
face.add_tag("oversized")
else:
face.add_tag("normal")
# 计算质量指标
face_count = len(faces)
avg_face_area = total_area / face_count if face_count > 0 else 0
edge_to_face_ratio = edge_count / face_count if face_count > 0 else 0
shell.set_metadata("thickness", thickness)
shell.set_metadata("face_count", face_count)
shell.set_metadata("edge_count", edge_count)
shell.set_metadata("total_area", total_area)
shell.set_metadata("avg_face_area", avg_face_area)
shell.set_metadata("edge_to_face_ratio", edge_to_face_ratio)
# 质量评估
if face_count < 20 and edge_to_face_ratio < 10:
shell.add_tag("good_quality")
elif face_count < 50 and edge_to_face_ratio < 20:
shell.add_tag("acceptable_quality")
else:
shell.add_tag("complex_geometry")
print(f"{name} 壳 (厚度 {thickness}):")
print(f" 面数: {face_count}")
print(f" 边数: {edge_count}")
print(f" 总面积: {total_area:.3f}")
print(f" 边面比: {edge_to_face_ratio:.2f}")
print(f" 质量: {shell.get_tags()}")
print()
except Exception as e:
print(f"创建 {name} 壳 (厚度 {thickness}) 失败: {e}")
print()
check_shell_quality()
```
### Shell Repair and Optimization
```python
from simplecadapi import make_box_rsolid, shell_rsolid
def optimize_shell():
"""优化壳的结构"""
# 创建基础壳
base_solid = make_box_rsolid(width=6, height=4, depth=3)
shell = shell_rsolid(base_solid, thickness=0.2)
shell.add_tag("original_shell")
faces = shell.get_faces()
# 分析和标记面
optimized_faces = []
removed_faces = []
total_area = sum(face.get_area() for face in faces)
avg_area = total_area / len(faces)
for i, face in enumerate(faces):
area = face.get_area()
face.add_tag(f"face_{i}")
face.set_metadata("original_area", area)
# 检查面的质量
if area < avg_area * 0.01: # 极小面
face.add_tag("tiny_face")
face.add_tag("candidate_for_removal")
removed_faces.append(face)
elif area > avg_area * 10: # 极大面
face.add_tag("oversized_face")
face.add_tag("needs_subdivision")
optimized_faces.append(face)
else:
face.add_tag("normal_face")
optimized_faces.append(face)
# 创建优化信息
shell.set_metadata("original_face_count", len(faces))
shell.set_metadata("optimized_face_count", len(optimized_faces))
shell.set_metadata("removed_face_count", len(removed_faces))
shell.set_metadata("total_area", total_area)
# 标记优化结果
if len(removed_faces) > 0:
shell.add_tag("has_removable_faces")
if len(optimized_faces) == len(faces):
shell.add_tag("well_formed")
else:
shell.add_tag("needs_optimization")
print(f"壳优化分析:")
print(f" 原始面数: {len(faces)}")
print(f" 优化后面数: {len(optimized_faces)}")
print(f" 移除面数: {len(removed_faces)}")
print(f" 总面积: {total_area:.3f}")
print(f" 平均面积: {avg_area:.3f}")
print(f" 优化标签: {shell.get_tags()}")
return shell, optimized_faces, removed_faces
shell, optimized_faces, removed_faces = optimize_shell()
```
### Shell Comparison and Analysis
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, shell_rsolid
def compare_shells():
"""比较不同类型的壳"""
# 创建不同的基础几何体
geometries = [
("box", make_box_rsolid(width=4, height=4, depth=4)),
("cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=4)),
]
shells = []
for name, geometry in geometries:
# 创建不同厚度的壳
for thickness in [0.1, 0.2, 0.3]:
shell = shell_rsolid(geometry, thickness=thickness)
shell.add_tag(f"{name}_shell")
shell.add_tag(f"thickness_{thickness}")
faces = shell.get_faces()
# 计算几何属性
total_area = sum(face.get_area() for face in faces)
face_count = len(faces)
# 计算复杂度指标
edge_count = sum(len(face.get_outer_wire().get_edges()) for face in faces)
complexity = edge_count / face_count if face_count > 0 else 0
shell.set_metadata("base_geometry", name)
shell.set_metadata("thickness", thickness)
shell.set_metadata("face_count", face_count)
shell.set_metadata("edge_count", edge_count)
shell.set_metadata("total_area", total_area)
shell.set_metadata("complexity", complexity)
# 分类复杂度
if complexity < 5:
shell.add_tag("simple")
elif complexity < 8:
shell.add_tag("moderate")
else:
shell.add_tag("complex")
shells.append(shell)
# 比较分析
print("壳比较分析:")
print("=" * 50)
for shell in shells:
base_geo = shell.get_metadata("base_geometry")
thickness = shell.get_metadata("thickness")
face_count = shell.get_metadata("face_count")
total_area = shell.get_metadata("total_area")
complexity = shell.get_metadata("complexity")
print(f"{base_geo} 壳 (厚度 {thickness}):")
print(f" 面数: {face_count}")
print(f" 总面积: {total_area:.3f}")
print(f" 复杂度: {complexity:.2f}")
print(f" 分类: {[tag for tag in shell.get_tags() if tag in ['simple', 'moderate', 'complex']]}")
print()
return shells
shell_comparison = compare_shells()
```
## String Representation
```python
from simplecadapi import make_box_rsolid, shell_rsolid
box = make_box_rsolid(width=5, height=3, depth=2)
shell = shell_rsolid(box, thickness=0.2)
shell.add_tag("example_shell")
shell.set_metadata("thickness", 0.2)
print(shell)
```
Output:
```
Shell:
face_count: 10
faces:
face_0:
area: 6.000
normal: [0.000, 0.000, 1.000]
outer_wire:
Wire:
edge_count: 4
closed: True
face_1:
area: 6.000
normal: [0.000, 0.000, -1.000]
outer_wire:
Wire:
edge_count: 4
closed: True
...
tags: [example_shell]
metadata:
thickness: 0.2
```
## Relationships with Other Geometry
- **Face (Face)**: Components of a shell
- **Solid (Solid)**: Closed shells can form solids
- **Compound (Compound)**: Multiple shells can form a compound
## Application Scenarios
- **Thin-walled structures**: Containers, housings, etc.
- **Architectural surfaces**: Building facades
- **Industrial design**: Product exterior design
- **Finite element analysis**: Mesh generation for thin-walled structures
## Notes
- Shells can be open or closed
- Closed shells can form solids
- Shell thickness affects the final geometry
- Complex shells may contain many small faces, affecting performance
- Shell quality directly affects the success rate of subsequent operations
- Certain operations may cause changes in shell topology
@@ -0,0 +1,278 @@
# SimpleWorkplane
## Overview
`SimpleWorkplane` is the workplane class in SimpleCAD API, used for defining local coordinate systems and workplanes for geometric modeling. It supports nested usage and can be used as a context manager, providing a local coordinate system environment for geometric operations.
## Class Definition
```python
class SimpleWorkplane:
"""工作平面上下文管理器
用于定义局部坐标系,支持嵌套使用
"""
```
## Usage
- Define local workplanes
- Provide context manager functionality, using with statements to automatically manage coordinate systems
- Support nested coordinate systems
- Conversion with CADQuery Workplane
- Simplify complex geometric modeling
## Constructor
### `__init__(origin, normal, x_dir)`
Initialize a workplane.
**Parameters:**
- `origin` (Tuple[float, float, float], optional): Workplane origin, default (0, 0, 0)
- `normal` (Tuple[float, float, float], optional): Workplane normal vector, default (0, 0, 1)
- `x_dir` (Tuple[float, float, float], optional): Workplane X-axis direction, default (1, 0, 0)
**Example:**
```python
from simplecadapi import SimpleWorkplane
# 默认工作平面(XY平面)
wp = SimpleWorkplane()
# 自定义工作平面
wp = SimpleWorkplane(
origin=(0, 0, 5),
normal=(0, 0, 1),
x_dir=(1, 0, 0)
)
# 倾斜的工作平面
wp = SimpleWorkplane(
origin=(0, 0, 0),
normal=(0, 1, 1), # 45度倾斜
x_dir=(1, 0, 0)
)
```
## Main Properties
- `cs`: Corresponding coordinate system of the workplane (CoordinateSystem)
- `cq_workplane`: CADQuery workplane object (lazy loaded)
## Common Methods
### Context Manager Methods
SimpleWorkplane supports `with` statements to automatically manage the current coordinate system within the context.
**Example:**
```python
from simplecadapi import SimpleWorkplane, make_box_rsolid
# 在全局坐标系中创建盒子
box1 = make_box_rsolid(1, 1, 1)
# 在局部坐标系中创建盒子
with SimpleWorkplane(origin=(2, 0, 0)) as wp:
box2 = make_box_rsolid(1, 1, 1) # 实际位置在 (2, 0, 0)
```
### `to_cq_workplane()`
Convert to CADQuery's Workplane object.
**Returns:**
- `cadquery.Workplane`: CADQuery workplane object
**Example:**
```python
from simplecadapi import SimpleWorkplane
wp = SimpleWorkplane(origin=(0, 0, 1))
cq_wp = wp.to_cq_workplane()
```
## Usage Examples
### Basic Usage
```python
from simplecadapi import SimpleWorkplane, make_circle_rface, extrude_rsolid
# 创建工作平面
wp = SimpleWorkplane(origin=(0, 0, 0), normal=(0, 0, 1))
# 使用工作平面
with wp:
# 在工作平面上创建圆形面
circle = make_circle_rface(radius=1.0)
# 拉伸成圆柱体
cylinder = extrude_rsolid(circle, height=2.0)
```
### Nested Workplanes
```python
from simplecadapi import SimpleWorkplane, make_box_rsolid
# 第一层工作平面
with SimpleWorkplane(origin=(1, 0, 0)) as wp1:
box1 = make_box_rsolid(1, 1, 1) # 位置在 (1, 0, 0)
# 第二层工作平面(相对于第一层)
with SimpleWorkplane(origin=(0, 1, 0)) as wp2:
box2 = make_box_rsolid(1, 1, 1) # 位置在 (1, 1, 0)
# 第三层工作平面(相对于第二层)
with SimpleWorkplane(origin=(0, 0, 1)) as wp3:
box3 = make_box_rsolid(1, 1, 1) # 位置在 (1, 1, 1)
```
### Creating Complex Geometry
```python
from simplecadapi import SimpleWorkplane, make_rectangle_rface, extrude_rsolid
# 创建一个带有多个特征的零件
def create_complex_part():
parts = []
# 主体
with SimpleWorkplane(origin=(0, 0, 0)) as wp:
base = make_rectangle_rface(width=10, height=5)
main_body = extrude_rsolid(base, height=2)
parts.append(main_body)
# 左侧支架
with SimpleWorkplane(origin=(-3, 0, 2), normal=(1, 0, 0)) as wp:
bracket = make_rectangle_rface(width=3, height=2)
left_bracket = extrude_rsolid(bracket, height=1)
parts.append(left_bracket)
# 右侧支架
with SimpleWorkplane(origin=(3, 0, 2), normal=(-1, 0, 0)) as wp:
bracket = make_rectangle_rface(width=3, height=2)
right_bracket = extrude_rsolid(bracket, height=1)
parts.append(right_bracket)
return parts
parts = create_complex_part()
```
### Rotated Workplanes
```python
import math
from simplecadapi import SimpleWorkplane, make_circle_rface, extrude_rsolid
# 创建多个旋转的工作平面
def create_rotated_cylinders():
cylinders = []
for i in range(6):
angle = i * math.pi / 3 # 每60度一个
# 创建旋转的工作平面
with SimpleWorkplane(
origin=(0, 0, 0),
normal=(math.cos(angle), math.sin(angle), 0),
x_dir=(0, 0, 1)
) as wp:
circle = make_circle_rface(radius=0.5)
cylinder = extrude_rsolid(circle, height=3)
cylinders.append(cylinder)
return cylinders
cylinders = create_rotated_cylinders()
```
### Creating Workplanes Along a Path
```python
from simplecadapi import SimpleWorkplane, make_box_rsolid
def create_path_features():
# 定义路径点
path_points = [
(0, 0, 0),
(1, 0, 0),
(2, 1, 0),
(3, 1, 1),
(4, 0, 1)
]
features = []
for i, point in enumerate(path_points):
# 计算朝向下一个点的方向
if i < len(path_points) - 1:
next_point = path_points[i + 1]
direction = (
next_point[0] - point[0],
next_point[1] - point[1],
next_point[2] - point[2]
)
else:
direction = (1, 0, 0) # 最后一个点使用默认方向
with SimpleWorkplane(origin=point, x_dir=direction) as wp:
feature = make_box_rsolid(0.2, 0.2, 0.2)
features.append(feature)
return features
features = create_path_features()
```
## Coordinate System Transformation
SimpleWorkplane automatically handles coordinate system transformations:
- Input coordinates and direction vectors are defined in the current coordinate system
- Internally converted to global coordinate system
- Automatically handles orthogonalization to ensure coordinate system correctness
## String Representation
```python
from simplecadapi import SimpleWorkplane
wp = SimpleWorkplane(origin=(1, 2, 3))
print(wp)
```
Output:
```
SimpleWorkplane:
coordinate_system:
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]
```
## CADQuery Compatibility
SimpleWorkplane can be seamlessly converted to CADQuery's Workplane:
```python
from simplecadapi import SimpleWorkplane
wp = SimpleWorkplane(origin=(0, 0, 1))
cq_wp = wp.to_cq_workplane()
# 可以在 CADQuery 工作平面上进行操作
cq_result = cq_wp.box(1, 1, 1)
```
## Notes
- When using `with` statements, the workplane automatically manages the coordinate system stack
- Coordinates in nested workplanes are relative to the parent workplane
- If the normal vector and X-axis direction are parallel, the system automatically selects an appropriate Y-axis direction
- Coordinate systems are automatically orthogonalized to ensure correctness
@@ -0,0 +1,697 @@
# Solid
## Overview
`Solid` is the solid class in the SimpleCAD API, representing a 3D closed geometry. Solids have volume and are one of the most important geometry types in CAD modeling. It wraps the CADQuery Solid object and adds tagging functionality and automatic face tagging capability.
## Class Definition
```python
class Solid(TaggedMixin):
"""实体类,包装CADQuery的Solid,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Represent 3D solid objects
- Perform boolean operations (union, intersection, difference)
- Apply feature operations (fillets, chamfers, etc.)
- Calculate physical properties such as volume and surface area
- Generate manufacturing data
## Constructor
### `__init__(cq_solid)`
Initializes a solid object.
**Parameters:**
- `cq_solid` (Union[cadquery.Solid, Any]): A CADQuery solid object or other Shape object
**Raises:**
- `ValueError`: When the input solid object is invalid
**Example:**
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid
)
# 通过 SimpleCAD 函数创建实体
box = make_box_rsolid(width=5, height=3, depth=2)
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=4)
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=1.5)
```
## Main Properties
- `cq_solid`: The underlying CADQuery solid object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
- `_face_tags`: Face tag dictionary (internal use)
## Common Methods
### `get_volume()`
Get the volume of the solid.
**Returns:**
- `float`: The volume of the solid
**Raises:**
- `ValueError`: When volume retrieval fails
**Example:**
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
import math
# 立方体体积
box = make_box_rsolid(width=2, height=3, depth=4)
box_volume = box.get_volume()
print(f"立方体体积: {box_volume}") # 24.0
# 圆柱体体积
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=5)
cylinder_volume = cylinder.get_volume()
expected_volume = math.pi * 2**2 * 5
print(f"圆柱体体积: {cylinder_volume:.3f}, 期望: {expected_volume:.3f}")
# 球体体积
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=1.5)
sphere_volume = sphere.get_volume()
expected_volume = (4/3) * math.pi * 1.5**3
print(f"球体体积: {sphere_volume:.3f}, 期望: {expected_volume:.3f}")
```
### `get_faces()`
Get all faces that make up the solid.
**Returns:**
- `List[Face]`: List of face objects
**Raises:**
- `ValueError`: When face list retrieval fails
**Example:**
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=4, height=3, depth=2)
faces = box.get_faces()
print(f"立方体有 {len(faces)} 个面")
for i, face in enumerate(faces):
area = face.get_area()
print(f"面 {i}: 面积 {area:.3f}")
```
### `get_edges()`
Get all edges that make up the solid.
**Returns:**
- `List[Edge]`: List of edge objects
**Raises:**
- `ValueError`: When edge list retrieval fails
**Example:**
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=4, height=3, depth=2)
edges = box.get_edges()
print(f"立方体有 {len(edges)} 条边")
for i, edge in enumerate(edges):
length = edge.get_length()
print(f"边 {i}: 长度 {length:.3f}")
```
### `auto_tag_faces(geometry_type)`
Automatically add tags to faces.
**Parameters:**
- `geometry_type` (str): Geometry type ("box", "cylinder", "sphere", "unknown")
**Example:**
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid
# 立方体面标记
box = make_box_rsolid(width=4, height=3, depth=2)
box.auto_tag_faces("box")
faces = box.get_faces()
for face in faces:
print(f"面标签: {face.get_tags()}")
# 圆柱体面标记
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=4)
cylinder.auto_tag_faces("cylinder")
faces = cylinder.get_faces()
for face in faces:
print(f"面标签: {face.get_tags()}")
```
### 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 Analyzing Basic Solids
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid
)
def create_basic_solids():
"""创建和分析基础实体"""
# 创建不同类型的实体
solids = [
("box", make_box_rsolid(width=4, height=3, depth=2)),
("cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=4)),
("sphere", make_sphere_rsolid(center=(0, 0, 0), radius=1.5))
]
for name, solid in solids:
# 添加基本标签
solid.add_tag(name)
solid.add_tag("basic_geometry")
# 自动标记面
solid.auto_tag_faces(name)
# 获取几何属性
volume = solid.get_volume()
faces = solid.get_faces()
edges = solid.get_edges()
# 计算表面积
total_surface_area = sum(face.get_area() for face in faces)
# 分析面的分布
face_areas = [face.get_area() for face in faces]
min_face_area = min(face_areas)
max_face_area = max(face_areas)
avg_face_area = sum(face_areas) / len(face_areas)
# 存储元数据
solid.set_metadata("volume", volume)
solid.set_metadata("surface_area", total_surface_area)
solid.set_metadata("face_count", len(faces))
solid.set_metadata("edge_count", len(edges))
solid.set_metadata("min_face_area", min_face_area)
solid.set_metadata("max_face_area", max_face_area)
solid.set_metadata("avg_face_area", avg_face_area)
# 计算体积效率(体积/表面积比)
volume_efficiency = volume / total_surface_area if total_surface_area > 0 else 0
solid.set_metadata("volume_efficiency", volume_efficiency)
print(f"{name.upper()} 实体分析:")
print(f" 体积: {volume:.3f}")
print(f" 表面积: {total_surface_area:.3f}")
print(f" 面数: {len(faces)}")
print(f" 边数: {len(edges)}")
print(f" 体积效率: {volume_efficiency:.3f}")
print(f" 面积范围: {min_face_area:.3f} - {max_face_area:.3f}")
print()
create_basic_solids()
```
### Boolean Operations Example
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
union_rsolidlist,
cut_rsolidlist,
intersect_rsolidlist
)
def boolean_operations_example():
"""布尔运算示例"""
# 创建基础几何体
box = make_box_rsolid(width=6, height=4, depth=3)
box.add_tag("base_box")
cylinder = make_cylinder_rsolid(center=(3, 2, 0), radius=1, height=5)
cylinder.add_tag("cutting_cylinder")
# 并集运算
union_result = union_rsolidlist([box, cylinder])[0]
union_result.add_tag("union_result")
union_result.add_tag("combined_geometry")
# 差集运算(从盒子中减去圆柱)
cut_result = cut_rsolidlist(box, cylinder)[0]
cut_result.add_tag("cut_result")
cut_result.add_tag("with_hole")
# 交集运算
intersect_result = intersect_rsolidlist(box, cylinder)[0]
intersect_result.add_tag("intersect_result")
intersect_result.add_tag("common_volume")
# 分析结果
operations = [
("原始盒子", box),
("原始圆柱", cylinder),
("并集", union_result),
("差集", cut_result),
("交集", intersect_result)
]
for name, solid in operations:
volume = solid.get_volume()
faces = solid.get_faces()
surface_area = sum(face.get_area() for face in faces)
solid.set_metadata("operation_type", name)
solid.set_metadata("volume", volume)
solid.set_metadata("surface_area", surface_area)
print(f"{name}:")
print(f" 体积: {volume:.3f}")
print(f" 表面积: {surface_area:.3f}")
print(f" 面数: {len(faces)}")
print(f" 标签: {solid.get_tags()}")
print()
boolean_operations_example()
```
### Feature Operations Example
```python
from simplecadapi import (
make_box_rsolid,
fillet_rsolid,
chamfer_rsolid,
select_edges_by_tag
)
def feature_operations_example():
"""特征操作示例"""
# 创建基础盒子
base_box = make_box_rsolid(width=8, height=6, depth=4)
base_box.add_tag("base_geometry")
base_box.auto_tag_faces("box")
# 分析原始几何体
original_volume = base_box.get_volume()
original_faces = base_box.get_faces()
original_edges = base_box.get_edges()
print(f"原始几何体:")
print(f" 体积: {original_volume:.3f}")
print(f" 面数: {len(original_faces)}")
print(f" 边数: {len(original_edges)}")
print()
# 圆角操作
try:
filleted_box = fillet_rsolid(base_box, radius=0.5)
filleted_box.add_tag("filleted")
filleted_box.add_tag("rounded_edges")
filleted_volume = filleted_box.get_volume()
filleted_faces = filleted_box.get_faces()
filleted_edges = filleted_box.get_edges()
filleted_box.set_metadata("original_volume", original_volume)
filleted_box.set_metadata("volume_change", filleted_volume - original_volume)
filleted_box.set_metadata("volume_ratio", filleted_volume / original_volume)
print(f"圆角后几何体:")
print(f" 体积: {filleted_volume:.3f}")
print(f" 体积变化: {filleted_volume - original_volume:.3f}")
print(f" 面数: {len(filleted_faces)}")
print(f" 边数: {len(filleted_edges)}")
print()
except Exception as e:
print(f"圆角操作失败: {e}")
filleted_box = None
# 倒角操作
try:
chamfered_box = chamfer_rsolid(base_box, distance=0.3)
chamfered_box.add_tag("chamfered")
chamfered_box.add_tag("beveled_edges")
chamfered_volume = chamfered_box.get_volume()
chamfered_faces = chamfered_box.get_faces()
chamfered_edges = chamfered_box.get_edges()
chamfered_box.set_metadata("original_volume", original_volume)
chamfered_box.set_metadata("volume_change", chamfered_volume - original_volume)
chamfered_box.set_metadata("volume_ratio", chamfered_volume / original_volume)
print(f"倒角后几何体:")
print(f" 体积: {chamfered_volume:.3f}")
print(f" 体积变化: {chamfered_volume - original_volume:.3f}")
print(f" 面数: {len(chamfered_faces)}")
print(f" 边数: {len(chamfered_edges)}")
print()
except Exception as e:
print(f"倒角操作失败: {e}")
chamfered_box = None
# 比较结果
results = [("原始", base_box)]
if filleted_box:
results.append(("圆角", filleted_box))
if chamfered_box:
results.append(("倒角", chamfered_box))
print("特征操作比较:")
for name, solid in results:
volume = solid.get_volume()
faces = solid.get_faces()
tags = solid.get_tags()
print(f" {name}: 体积={volume:.3f}, 面数={len(faces)}, 标签={tags}")
feature_operations_example()
```
### Creating Complex Geometry
```python
from simplecadapi import (
make_box_rsolid,
make_cylinder_rsolid,
make_sphere_rsolid,
union_rsolidlist,
cut_rsolidlist,
translate_shape,
rotate_shape
)
def create_complex_geometry():
"""创建复杂几何体"""
# 创建主体
main_body = make_box_rsolid(width=12, height=8, depth=6)
main_body.add_tag("main_body")
main_body.add_tag("base_structure")
# 创建圆柱形孔
holes = []
hole_positions = [(3, 2, 0), (9, 2, 0), (3, 6, 0), (9, 6, 0)]
for i, (x, y, z) in enumerate(hole_positions):
hole = make_cylinder_rsolid(center=(x, y, z), radius=0.8, height=8)
hole.add_tag(f"hole_{i}")
hole.add_tag("mounting_hole")
holes.append(hole)
# 创建球形特征
sphere_feature = make_sphere_rsolid(center=(6, 4, 6), radius=2)
sphere_feature.add_tag("sphere_feature")
sphere_feature.add_tag("decorative")
# 创建圆柱形支柱
support_cylinder = make_cylinder_rsolid(center=(6, 4, 0), radius=1, height=4)
support_cylinder.add_tag("support_cylinder")
support_cylinder.add_tag("structural")
# 组合几何体
complex_solid = union_rsolidlist([main_body, sphere_feature])[0]
complex_solid = union_rsolidlist([complex_solid, support_cylinder])[0]
# 减去孔
for hole in holes:
complex_solid = cut_rsolidlist(complex_solid, hole)[0]
# 标记复杂几何体
complex_solid.add_tag("complex_geometry")
complex_solid.add_tag("multi_feature")
complex_solid.add_tag("machined_part")
# 分析复杂几何体
volume = complex_solid.get_volume()
faces = complex_solid.get_faces()
edges = complex_solid.get_edges()
# 计算几何复杂度
face_count = len(faces)
edge_count = len(edges)
complexity_ratio = edge_count / face_count if face_count > 0 else 0
# 分析面的分布
face_areas = [face.get_area() for face in faces]
total_surface_area = sum(face_areas)
# 分类面
small_faces = [f for f in faces if f.get_area() < 1.0]
large_faces = [f for f in faces if f.get_area() > 10.0]
medium_faces = [f for f in faces if 1.0 <= f.get_area() <= 10.0]
# 存储分析结果
complex_solid.set_metadata("volume", volume)
complex_solid.set_metadata("surface_area", total_surface_area)
complex_solid.set_metadata("face_count", face_count)
complex_solid.set_metadata("edge_count", edge_count)
complex_solid.set_metadata("complexity_ratio", complexity_ratio)
complex_solid.set_metadata("small_face_count", len(small_faces))
complex_solid.set_metadata("medium_face_count", len(medium_faces))
complex_solid.set_metadata("large_face_count", len(large_faces))
# 根据复杂度添加标签
if complexity_ratio < 5:
complex_solid.add_tag("simple_topology")
elif complexity_ratio < 10:
complex_solid.add_tag("moderate_topology")
else:
complex_solid.add_tag("complex_topology")
print(f"复杂几何体分析:")
print(f" 体积: {volume:.3f}")
print(f" 表面积: {total_surface_area:.3f}")
print(f" 面数: {face_count}")
print(f" 边数: {edge_count}")
print(f" 复杂度比: {complexity_ratio:.2f}")
print(f" 面分布 - 小:{len(small_faces)}, 中:{len(medium_faces)}, 大:{len(large_faces)}")
print(f" 标签: {complex_solid.get_tags()}")
return complex_solid
complex_geometry = create_complex_geometry()
```
### Solid Quality Analysis
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
def analyze_solid_quality():
"""分析实体质量"""
# 创建测试实体
test_solids = [
("small_box", make_box_rsolid(width=1, height=1, depth=1)),
("large_box", make_box_rsolid(width=10, height=10, depth=10)),
("thin_box", make_box_rsolid(width=10, height=10, depth=0.1)),
("cylinder", make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=5)),
("sphere", make_sphere_rsolid(center=(0, 0, 0), radius=2))
]
for name, solid in test_solids:
solid.add_tag(name)
solid.add_tag("test_geometry")
# 基本几何属性
volume = solid.get_volume()
faces = solid.get_faces()
edges = solid.get_edges()
# 计算质量指标
surface_area = sum(face.get_area() for face in faces)
volume_to_surface_ratio = volume / surface_area if surface_area > 0 else 0
# 分析拓扑复杂度
face_count = len(faces)
edge_count = len(edges)
euler_characteristic = None # 简化版本不计算欧拉特征数
# 面积分布分析
face_areas = [face.get_area() for face in faces]
if face_areas:
min_area = min(face_areas)
max_area = max(face_areas)
area_ratio = max_area / min_area if min_area > 0 else float('inf')
else:
min_area = max_area = area_ratio = 0
# 质量评估
quality_score = 0
quality_issues = []
# 体积检查
if volume > 1e-6:
quality_score += 20
else:
quality_issues.append("极小体积")
# 面数检查
if 4 <= face_count <= 100:
quality_score += 20
elif face_count > 100:
quality_issues.append("面数过多")
else:
quality_issues.append("面数异常")
# 面积比检查
if area_ratio < 1000:
quality_score += 20
else:
quality_issues.append("面积差异过大")
# 体积效率检查
if volume_to_surface_ratio > 0.1:
quality_score += 20
else:
quality_issues.append("体积效率低")
# 边数合理性检查
if edge_count < face_count * 10:
quality_score += 20
else:
quality_issues.append("边数过多")
# 存储质量数据
solid.set_metadata("volume", volume)
solid.set_metadata("surface_area", surface_area)
solid.set_metadata("face_count", face_count)
solid.set_metadata("edge_count", edge_count)
solid.set_metadata("volume_to_surface_ratio", volume_to_surface_ratio)
solid.set_metadata("area_ratio", area_ratio)
solid.set_metadata("quality_score", quality_score)
solid.set_metadata("quality_issues", quality_issues)
# 质量标签
if quality_score >= 80:
solid.add_tag("high_quality")
elif quality_score >= 60:
solid.add_tag("good_quality")
elif quality_score >= 40:
solid.add_tag("acceptable_quality")
else:
solid.add_tag("poor_quality")
print(f"{name.upper()} 质量分析:")
print(f" 体积: {volume:.6f}")
print(f" 表面积: {surface_area:.3f}")
print(f" 面数: {face_count}")
print(f" 边数: {edge_count}")
print(f" 体积效率: {volume_to_surface_ratio:.3f}")
print(f" 面积比: {area_ratio:.2f}")
print(f" 质量分数: {quality_score}/100")
if quality_issues:
print(f" 质量问题: {', '.join(quality_issues)}")
print(f" 质量等级: {[tag for tag in solid.get_tags() if 'quality' in tag]}")
print()
analyze_solid_quality()
```
## String Representation
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=5, height=3, depth=2)
box.add_tag("example_box")
box.auto_tag_faces("box")
box.set_metadata("material", "aluminum")
print(box)
```
Output:
```
Solid:
volume: 30.000
face_count: 6
edge_count: 12
faces:
face_0:
area: 15.000
normal: [0.000, 0.000, 1.000]
tags: [top]
face_1:
area: 15.000
normal: [0.000, 0.000, -1.000]
tags: [bottom]
face_2:
area: 10.000
normal: [0.000, 1.000, 0.000]
tags: [front]
face_3:
area: 10.000
normal: [0.000, -1.000, 0.000]
tags: [back]
face_4:
area: 6.000
normal: [1.000, 0.000, 0.000]
tags: [right]
face_5:
area: 6.000
normal: [-1.000, 0.000, 0.000]
tags: [left]
tags: [example_box]
metadata:
material: aluminum
```
## Relationships with Other Geometry
- **Face (Face)**: Boundary surfaces of a solid
- **Edge (Edge)**: Boundaries of faces
- **Shell (Shell)**: Solids can be decomposed into shells
- **Compound (Compound)**: Multiple solids can form a compound
## Application Scenarios
- **Mechanical design**: Part modeling
- **Architectural design**: Building components
- **Product design**: Industrial products
- **3D printing**: Prototype manufacturing
- **Simulation analysis**: Finite element analysis
## Notes
- Solids must be closed, valid geometry
- Boolean operations may change the solid's topology
- Complex solids may contain many faces and edges
- Feature operations may fail and require appropriate error handling
- Automatic face tagging depends on geometry regularity
- Solid quality directly affects the success rate and performance of subsequent operations
@@ -0,0 +1,692 @@
# TaggedMixin
## Overview
`TaggedMixin` is the tag mixin class in SimpleCAD API, providing unified tag and metadata management functionality for all geometry classes. It is a mixin class inherited by all core geometry classes, implementing the core functionality of the tag system.
## Class Definition
```python
class TaggedMixin:
"""标签混入类,为几何体提供标签功能"""
```
## Usage
- Add identification tags to geometries
- Store and manage metadata
- Implement geometry classification and queries
- Support complex filtering and batch operations
- Provide semantic information for geometries
## Initialization
### `__init__()`
Initialize the tag mixin class.
**Description:**
- Creates an empty tag set `_tags`
- Creates an empty metadata dictionary `_metadata`
- Usually called in the `__init__` method of inheriting classes
**Example:**
```python
class CustomGeometry(TaggedMixin):
def __init__(self):
TaggedMixin.__init__(self)
# 其他初始化代码
```
## Main Properties
- `_tags`: Tag set (Set[str]), storing geometry tags
- `_metadata`: Metadata dictionary (Dict[str, Any]), storing key-value pairs
## Tag Management Methods
### `add_tag(tag)`
Add a tag to the geometry.
**Parameters:**
- `tag` (str): Name of the tag to add
**Exceptions:**
- `TypeError`: Raised when the tag is not a string type
**Example:**
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=5, height=3, depth=2)
box.add_tag("structural")
box.add_tag("main_component")
box.add_tag("steel_part")
print(box.get_tags()) # {'structural', 'main_component', 'steel_part'}
```
### `remove_tag(tag)`
Remove a specified tag from the geometry.
**Parameters:**
- `tag` (str): Name of the tag to remove
**Description:**
- If the tag does not exist, no exception will be raised
- Uses the `discard` method for safe removal
**Example:**
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=5, height=3, depth=2)
box.add_tag("temporary")
box.add_tag("structural")
box.remove_tag("temporary")
print(box.get_tags()) # {'structural'}
# 移除不存在的标签不会出错
box.remove_tag("nonexistent")
```
### `has_tag(tag)`
Check if the geometry has a specified tag.
**Parameters:**
- `tag` (str): Name of the tag to check
**Returns:**
- `bool`: Returns True if the tag exists, False otherwise
**Example:**
```python
from simplecadapi import make_cylinder_rsolid
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=5)
cylinder.add_tag("rotational")
cylinder.add_tag("hollow_ready")
if cylinder.has_tag("rotational"):
print("这是一个旋转体")
if not cylinder.has_tag("solid"):
cylinder.add_tag("solid")
```
### `get_tags()`
Get all tags of the geometry.
**Returns:**
- `Set[str]`: A copy of the tag set
**Description:**
- Returns a copy of the tag set; modifying the return value will not affect the original tags
**Example:**
```python
from simplecadapi import make_sphere_rsolid
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=1.5)
sphere.add_tag("curved")
sphere.add_tag("symmetric")
sphere.add_tag("smooth")
tags = sphere.get_tags()
print(f"球体标签: {sorted(tags)}") # ['curved', 'smooth', 'symmetric']
# 修改返回的集合不会影响原始标签
tags.add("new_tag")
print(f"原始标签仍然是: {sorted(sphere.get_tags())}")
```
## Metadata Management Methods
### `set_metadata(key, value)`
Set a metadata key-value pair.
**Parameters:**
- `key` (str): Metadata key name
- `value` (Any): Metadata value, can be any type
**Example:**
```python
from simplecadapi import make_box_rsolid
box = make_box_rsolid(width=5, height=3, depth=2)
# 设置各种类型的元数据
box.set_metadata("material", "aluminum")
box.set_metadata("density", 2.7)
box.set_metadata("cost", 45.50)
box.set_metadata("dimensions", [5, 3, 2])
box.set_metadata("manufactured_date", "2024-01-15")
box.set_metadata("is_finished", True)
box.set_metadata("supplier", {"name": "ABC Corp", "contact": "info@abc.com"})
```
### `get_metadata(key, default=None)`
Get the metadata value for a specified key.
**Parameters:**
- `key` (str): Metadata key name
- `default` (Any, optional): Default value, returned when the key does not exist
**Returns:**
- `Any`: Metadata value or default value
**Example:**
```python
from simplecadapi import make_cylinder_rsolid
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=2, height=5)
cylinder.set_metadata("material", "steel")
cylinder.set_metadata("weight", 12.5)
# 获取存在的元数据
material = cylinder.get_metadata("material")
print(f"材料: {material}") # 材料: steel
weight = cylinder.get_metadata("weight")
print(f"重量: {weight}") # 重量: 12.5
# 获取不存在的元数据
color = cylinder.get_metadata("color", "default_color")
print(f"颜色: {color}") # 颜色: default_color
# 不提供默认值时返回 None
surface_finish = cylinder.get_metadata("surface_finish")
print(f"表面处理: {surface_finish}") # 表面处理: None
```
## Internal Helper Methods
### `_format_tags_and_metadata(indent=0)`
Format the string representation of tags and metadata.
**Parameters:**
- `indent` (int): Indentation level
**Returns:**
- `str`: Formatted string
**Description:**
- Internal method, used for implementing `__str__` method
- Generates aesthetically pleasing tag and metadata display format
## Usage Examples
### Basic Tag Usage
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
def basic_tagging_example():
"""基础标签使用示例"""
# 创建几何体
box = make_box_rsolid(width=4, height=3, depth=2)
cylinder = make_cylinder_rsolid(center=(0, 0, 0), radius=1.5, height=4)
sphere = make_sphere_rsolid(center=(0, 0, 0), radius=1)
# 添加几何类型标签
box.add_tag("rectangular")
box.add_tag("prismatic")
cylinder.add_tag("cylindrical")
cylinder.add_tag("rotational")
sphere.add_tag("spherical")
sphere.add_tag("curved")
# 添加功能标签
box.add_tag("structural")
cylinder.add_tag("pipe")
sphere.add_tag("bearing")
# 添加材料标签
for geom in [box, cylinder, sphere]:
geom.add_tag("metal")
geom.add_tag("machinable")
# 检查和使用标签
geometries = [("盒子", box), ("圆柱", cylinder), ("球体", sphere)]
for name, geom in geometries:
print(f"{name} 标签: {sorted(geom.get_tags())}")
if geom.has_tag("rotational"):
print(f" -> {name} 是旋转对称的")
if geom.has_tag("structural"):
print(f" -> {name} 用于结构支撑")
basic_tagging_example()
```
### Advanced Metadata Management
```python
from simplecadapi import make_box_rsolid
import datetime
def advanced_metadata_example():
"""高级元数据管理示例"""
# 创建零件
part = make_box_rsolid(width=10, height=8, depth=5)
part.add_tag("machined_part")
part.add_tag("aluminum")
# 设置基本属性
part.set_metadata("part_number", "MP-001-A")
part.set_metadata("revision", "Rev-C")
part.set_metadata("material", "6061-T6 Aluminum")
part.set_metadata("density", 2.7) # g/cm³
# 设置尺寸信息
dimensions = {
"length": 10,
"width": 8,
"height": 5,
"tolerance": "±0.1"
}
part.set_metadata("dimensions", dimensions)
# 设置制造信息
manufacturing = {
"process": "CNC Milling",
"machine": "Haas VF-2",
"operator": "John Doe",
"setup_time": 45, # minutes
"cycle_time": 23 # minutes
}
part.set_metadata("manufacturing", manufacturing)
# 设置质量控制信息
quality = {
"inspection_level": "Level II",
"critical_dimensions": ["length", "width"],
"surface_finish": "Ra 1.6 μm",
"hardness": "HB 95-105"
}
part.set_metadata("quality", quality)
# 设置成本信息
cost_breakdown = {
"material_cost": 15.50,
"machining_cost": 45.00,
"setup_cost": 25.00,
"overhead": 12.50,
"total": 98.00
}
part.set_metadata("cost", cost_breakdown)
# 设置时间信息
part.set_metadata("created_date", datetime.datetime.now().isoformat())
part.set_metadata("due_date", "2024-02-15")
part.set_metadata("estimated_completion", "2024-02-10")
# 计算衍生属性
volume = part.get_volume()
density = part.get_metadata("density")
weight = volume * density / 1000 # 转换为kg(假设体积单位为cm³)
part.set_metadata("calculated_weight", weight)
part.set_metadata("weight_unit", "kg")
# 生成报告
print("零件详细信息报告:")
print("=" * 50)
# 基本信息
print(f"零件号: {part.get_metadata('part_number')}")
print(f"版本: {part.get_metadata('revision')}")
print(f"材料: {part.get_metadata('material')}")
print(f"重量: {weight:.3f} kg")
# 尺寸信息
dims = part.get_metadata("dimensions")
print(f"\n尺寸:")
print(f" 长x宽x高: {dims['length']} x {dims['width']} x {dims['height']}")
print(f" 公差: {dims['tolerance']}")
# 制造信息
mfg = part.get_metadata("manufacturing")
print(f"\n制造:")
print(f" 工艺: {mfg['process']}")
print(f" 设备: {mfg['machine']}")
print(f" 周期时间: {mfg['cycle_time']} 分钟")
# 成本信息
cost = part.get_metadata("cost")
print(f"\n成本:")
print(f" 材料: ${cost['material_cost']:.2f}")
print(f" 加工: ${cost['machining_cost']:.2f}")
print(f" 总计: ${cost['total']:.2f}")
return part
advanced_part = advanced_metadata_example()
```
### Batch Tag and Metadata Operations
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid
def batch_tagging_example():
"""批量标签和元数据操作示例"""
# 创建零件集合
parts = []
# 创建不同类型的零件
part_specs = [
("housing", "box", (10, 8, 6), "aluminum"),
("shaft", "cylinder", (1, 15), "steel"),
("bracket", "box", (5, 3, 2), "aluminum"),
("bushing", "cylinder", (0.5, 3), "bronze"),
("plate", "box", (8, 6, 0.5), "steel")
]
for name, shape_type, dimensions, material in part_specs:
if shape_type == "box":
part = make_box_rsolid(width=dimensions[0], height=dimensions[1], depth=dimensions[2])
else: # cylinder
part = make_cylinder_rsolid(center=(0, 0, 0), radius=dimensions[0], height=dimensions[1])
# 基本标签
part.add_tag(name)
part.add_tag(shape_type)
part.add_tag(material)
# 基本元数据
part.set_metadata("name", name)
part.set_metadata("material", material)
part.set_metadata("shape_type", shape_type)
parts.append(part)
# 批量添加通用标签
for part in parts:
part.add_tag("mechanical_part")
part.add_tag("machined")
part.set_metadata("project", "Assembly_001")
part.set_metadata("status", "design")
# 根据材料批量设置属性
material_properties = {
"aluminum": {"density": 2.7, "cost_per_kg": 3.50, "color": "silver"},
"steel": {"density": 7.8, "cost_per_kg": 1.20, "color": "gray"},
"bronze": {"density": 8.9, "cost_per_kg": 8.50, "color": "bronze"}
}
for part in parts:
material = part.get_metadata("material")
if material in material_properties:
props = material_properties[material]
for prop_name, prop_value in props.items():
part.set_metadata(prop_name, prop_value)
# 根据尺寸批量分类
for part in parts:
volume = part.get_volume()
if volume < 10:
part.add_tag("small_part")
part.set_metadata("size_category", "small")
elif volume < 100:
part.add_tag("medium_part")
part.set_metadata("size_category", "medium")
else:
part.add_tag("large_part")
part.set_metadata("size_category", "large")
# 批量计算成本
for part in parts:
volume = part.get_volume()
density = part.get_metadata("density", 1.0)
cost_per_kg = part.get_metadata("cost_per_kg", 2.0)
weight = volume * density / 1000 # 转换为kg
material_cost = weight * cost_per_kg
part.set_metadata("weight", weight)
part.set_metadata("material_cost", material_cost)
# 批量查询和统计
print("批量操作结果:")
print("=" * 40)
# 按材料统计
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_weight = sum(p.get_metadata("weight", 0) for p in material_parts)
total_cost = sum(p.get_metadata("material_cost", 0) for p in material_parts)
print(f"{material.upper()} 零件:")
print(f" 数量: {len(material_parts)}")
print(f" 总重量: {total_weight:.3f} kg")
print(f" 材料成本: ${total_cost:.2f}")
print()
# 按尺寸分类统计
size_categories = ["small", "medium", "large"]
for size in size_categories:
size_parts = [p for p in parts if p.has_tag(f"{size}_part")]
if size_parts:
avg_volume = sum(p.get_volume() for p in size_parts) / len(size_parts)
print(f"{size.upper()} 零件: {len(size_parts)} 个, 平均体积: {avg_volume:.3f}")
return parts
batch_parts = batch_tagging_example()
```
### Advanced Query and Filtering
```python
from simplecadapi import make_box_rsolid, make_cylinder_rsolid, make_sphere_rsolid
def advanced_query_example():
"""高级查询和筛选示例"""
# 创建复杂的零件库
parts_library = []
# 定义零件类型
part_definitions = [
# (name, shape, dimensions, material, application, priority)
("main_frame", "box", (20, 15, 10), "steel", "structural", "high"),
("support_beam", "box", (15, 5, 5), "aluminum", "structural", "medium"),
("motor_shaft", "cylinder", (2, 25), "steel", "rotating", "high"),
("bearing_housing", "cylinder", (3, 8), "aluminum", "support", "medium"),
("control_sphere", "sphere", (2,), "plastic", "interface", "low"),
("sensor_mount", "box", (3, 3, 2), "aluminum", "mounting", "medium"),
("drive_wheel", "cylinder", (5, 3), "rubber", "motion", "high"),
("safety_cover", "box", (12, 8, 1), "plastic", "protection", "low")
]
for name, shape, dims, material, application, priority in part_definitions:
# 创建几何体
if shape == "box":
part = make_box_rsolid(width=dims[0], height=dims[1], depth=dims[2])
elif shape == "cylinder":
part = make_cylinder_rsolid(center=(0, 0, 0), radius=dims[0], height=dims[1])
else: # sphere
part = make_sphere_rsolid(center=(0, 0, 0), radius=dims[0])
# 添加标签
part.add_tag(name)
part.add_tag(shape)
part.add_tag(material)
part.add_tag(application)
part.add_tag(priority)
# 设置详细元数据
part.set_metadata("name", name)
part.set_metadata("shape", shape)
part.set_metadata("material", material)
part.set_metadata("application", application)
part.set_metadata("priority", priority)
part.set_metadata("volume", part.get_volume())
# 根据应用设置其他属性
if application == "structural":
part.add_tag("load_bearing")
part.set_metadata("safety_factor", 2.5)
elif application == "rotating":
part.add_tag("dynamic")
part.set_metadata("max_rpm", 3600)
elif application == "interface":
part.add_tag("user_contact")
part.set_metadata("ergonomic", True)
# 根据材料设置属性
material_db = {
"steel": {"density": 7.8, "strength": "high", "cost": 1.2},
"aluminum": {"density": 2.7, "strength": "medium", "cost": 3.5},
"plastic": {"density": 1.2, "strength": "low", "cost": 0.8},
"rubber": {"density": 1.5, "strength": "flexible", "cost": 2.0}
}
if material in material_db:
for prop, value in material_db[material].items():
part.set_metadata(prop, value)
parts_library.append(part)
# 定义查询函数
def query_parts(parts, **criteria):
"""根据条件查询零件"""
result = parts
for key, value in criteria.items():
if key == "has_tag":
result = [p for p in result if p.has_tag(value)]
elif key == "has_any_tag":
result = [p for p in result if any(p.has_tag(tag) for tag in value)]
elif key == "has_all_tags":
result = [p for p in result if all(p.has_tag(tag) for tag in value)]
elif key == "metadata_equals":
meta_key, meta_value = value
result = [p for p in result if p.get_metadata(meta_key) == meta_value]
elif key == "metadata_greater":
meta_key, threshold = value
result = [p for p in result if p.get_metadata(meta_key, 0) > threshold]
elif key == "metadata_less":
meta_key, threshold = value
result = [p for p in result if p.get_metadata(meta_key, float('inf')) < threshold]
elif key == "volume_range":
min_vol, max_vol = value
result = [p for p in result if min_vol <= p.get_volume() <= max_vol]
return result
# 执行各种查询
print("高级查询示例:")
print("=" * 50)
# 1. 简单标签查询
steel_parts = query_parts(parts_library, has_tag="steel")
print(f"1. 钢制零件: {len(steel_parts)} 个")
for part in steel_parts:
print(f" - {part.get_metadata('name')}")
# 2. 多标签查询
structural_aluminum = query_parts(parts_library, has_all_tags=["aluminum", "structural"])
print(f"\n2. 铝制结构件: {len(structural_aluminum)} 个")
# 3. 元数据查询
high_priority = query_parts(parts_library, metadata_equals=("priority", "high"))
print(f"\n3. 高优先级零件: {len(high_priority)} 个")
for part in high_priority:
print(f" - {part.get_metadata('name')}: {part.get_metadata('application')}")
# 4. 范围查询
large_parts = query_parts(parts_library, volume_range=(100, 5000))
print(f"\n4. 大型零件 (体积 100-5000): {len(large_parts)} 个")
# 5. 复合查询
critical_metal_parts = query_parts(
parts_library,
has_any_tag=["steel", "aluminum"],
metadata_equals=("priority", "high")
)
print(f"\n5. 关键金属零件: {len(critical_metal_parts)} 个")
# 6. 性能分析查询
load_bearing = query_parts(parts_library, has_tag="load_bearing")
if load_bearing:
avg_safety_factor = sum(p.get_metadata("safety_factor", 1) for p in load_bearing) / len(load_bearing)
print(f"\n6. 承载零件平均安全系数: {avg_safety_factor:.2f}")
# 7. 成本分析
total_cost = 0
cost_by_material = {}
for part in parts_library:
volume = part.get_volume()
density = part.get_metadata("density", 1)
cost_per_kg = part.get_metadata("cost", 1)
weight = volume * density / 1000
cost = weight * cost_per_kg
total_cost += cost
material = part.get_metadata("material")
if material not in cost_by_material:
cost_by_material[material] = 0
cost_by_material[material] += cost
print(f"\n7. 成本分析:")
print(f" 总成本: ${total_cost:.2f}")
for material, cost in cost_by_material.items():
print(f" {material}: ${cost:.2f}")
return parts_library
parts_db = advanced_query_example()
```
## Best Practices
### Tag Naming Conventions
1. **Consistency**: Use consistent naming styles
2. **Hierarchy**: Use dot-separated hierarchical structures (e.g., `material.metal.steel`)
3. **Descriptive**: Tag names should be descriptive
4. **Avoid Spaces**: Use underscores instead of spaces
### Metadata Organization
1. **Structured**: Use dictionaries to organize related metadata
2. **Type Consistency**: Use the same data type for metadata with similar purposes
3. **Clear Units**: Numerical metadata should specify units
4. **Version Control**: Important metadata should include version information
### Performance Considerations
1. **Tag Quantity**: Avoid excessive tags that may impact query performance
2. **Metadata Size**: Avoid storing large data objects
3. **Query Optimization**: Organize tags and metadata appropriately to support efficient queries
4. **Memory Management**: Clean up unnecessary tags and metadata in a timely manner
## Notes
- Tags must be of string type
- Tags are case-sensitive
- Metadata values can be of any type, but serializable types are recommended
- Tag and metadata modifications do not trigger geometry updates
- Special handling is required for tags and metadata during deep copying
- Tags and metadata do not participate in geometric operations; they are only used for management and queries
@@ -0,0 +1,320 @@
# Vertex
## Overview
`Vertex` is the vertex class in SimpleCAD API, representing a point in 3D space. It wraps CADQuery's Vertex object and adds tag functionality for identifying and managing specific vertices in geometries.
## Class Definition
```python
class Vertex(TaggedMixin):
"""顶点类,包装CADQuery的Vertex,添加标签功能"""
```
## Inheritance Relationships
- Inherits from `TaggedMixin`, with tag and metadata functionality
## Usage
- Represent points in 3D space
- Serve as building elements for edges, wires, faces, and other geometries
- Provide vertex coordinate information
- Support tag management and queries
## Constructor
### `__init__(cq_vertex)`
Initialize a vertex object.
**Parameters:**
- `cq_vertex` (cadquery.Vertex): CADQuery vertex object
**Exceptions:**
- `ValueError`: Raised when the input vertex object is invalid
**Example:**
```python
from simplecadapi import make_point_rvertex
# 通过 SimpleCAD 函数创建顶点
vertex = make_point_rvertex(1.0, 2.0, 3.0)
```
## Main Properties
- `cq_vertex`: Underlying CADQuery vertex object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_coordinates()`
Get the coordinates of the vertex.
**Returns:**
- `Tuple[float, float, float]`: Vertex coordinates (x, y, z)
**Exceptions:**
- `ValueError`: Raised when coordinate retrieval fails
**Example:**
```python
from simplecadapi import make_point_rvertex
vertex = make_point_rvertex(1.0, 2.0, 3.0)
coords = vertex.get_coordinates()
print(coords) # (1.0, 2.0, 3.0)
```
### Tag Management Methods
Methods inherited from `TaggedMixin`:
#### `add_tag(tag)`
Add a tag.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.add_tag("origin")
vertex.add_tag("reference_point")
```
#### `has_tag(tag)`
Check if a specified tag exists.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.add_tag("origin")
if vertex.has_tag("origin"):
print("这是原点")
```
#### `get_tags()`
Get all tags.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.add_tag("origin")
vertex.add_tag("reference")
tags = vertex.get_tags()
print(tags) # {'origin', 'reference'}
```
#### `remove_tag(tag)`
Remove a tag.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.add_tag("temp")
vertex.remove_tag("temp")
```
### Metadata Management Methods
#### `set_metadata(key, value)`
Set metadata.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.set_metadata("created_by", "user_input")
vertex.set_metadata("importance", "high")
```
#### `get_metadata(key, default=None)`
Get metadata.
**Example:**
```python
vertex = make_point_rvertex(0, 0, 0)
vertex.set_metadata("created_by", "user_input")
creator = vertex.get_metadata("created_by")
print(creator) # "user_input"
unknown = vertex.get_metadata("unknown_key", "default_value")
print(unknown) # "default_value"
```
## Usage Examples
### Creating and Using Vertices
```python
from simplecadapi import make_point_rvertex
# 创建顶点
vertex1 = make_point_rvertex(0, 0, 0)
vertex2 = make_point_rvertex(1, 1, 1)
# 获取坐标
coords1 = vertex1.get_coordinates()
coords2 = vertex2.get_coordinates()
print(f"顶点1坐标: {coords1}") # 顶点1坐标: (0.0, 0.0, 0.0)
print(f"顶点2坐标: {coords2}") # 顶点2坐标: (1.0, 1.0, 1.0)
```
### Vertex Tag Management
```python
from simplecadapi import make_point_rvertex
# 创建关键点
origin = make_point_rvertex(0, 0, 0)
corner1 = make_point_rvertex(10, 0, 0)
corner2 = make_point_rvertex(10, 10, 0)
corner3 = make_point_rvertex(0, 10, 0)
# 添加标签
origin.add_tag("origin")
origin.add_tag("reference")
corner1.add_tag("corner")
corner1.add_tag("x_axis")
corner2.add_tag("corner")
corner2.add_tag("diagonal")
corner3.add_tag("corner")
corner3.add_tag("y_axis")
# 查找所有角点
vertices = [origin, corner1, corner2, corner3]
corners = [v for v in vertices if v.has_tag("corner")]
print(f"找到 {len(corners)} 个角点")
```
### Vertex Classification and Management
```python
from simplecadapi import make_point_rvertex
def create_grid_vertices(width, height, spacing):
"""创建网格顶点"""
vertices = []
for i in range(width + 1):
for j in range(height + 1):
x = i * spacing
y = j * spacing
z = 0
vertex = make_point_rvertex(x, y, z)
# 添加位置标签
if i == 0 and j == 0:
vertex.add_tag("origin")
elif i == 0:
vertex.add_tag("left_edge")
elif i == width:
vertex.add_tag("right_edge")
if j == 0:
vertex.add_tag("bottom_edge")
elif j == height:
vertex.add_tag("top_edge")
# 添加角点标签
if (i == 0 or i == width) and (j == 0 or j == height):
vertex.add_tag("corner")
# 添加元数据
vertex.set_metadata("grid_position", (i, j))
vertex.set_metadata("distance_from_origin", (x*x + y*y)**0.5)
vertices.append(vertex)
return vertices
# 创建 5x3 网格
vertices = create_grid_vertices(5, 3, 1.0)
# 查找特定顶点
corners = [v for v in vertices if v.has_tag("corner")]
origin = [v for v in vertices if v.has_tag("origin")][0]
print(f"网格顶点总数: {len(vertices)}")
print(f"角点数量: {len(corners)}")
print(f"原点坐标: {origin.get_coordinates()}")
```
### Vertex Distance Calculation
```python
import math
from simplecadapi import make_point_rvertex
def calculate_distance(vertex1, vertex2):
"""计算两个顶点之间的距离"""
coords1 = vertex1.get_coordinates()
coords2 = vertex2.get_coordinates()
dx = coords2[0] - coords1[0]
dy = coords2[1] - coords1[1]
dz = coords2[2] - coords1[2]
return math.sqrt(dx*dx + dy*dy + dz*dz)
# 创建顶点
v1 = make_point_rvertex(0, 0, 0)
v2 = make_point_rvertex(3, 4, 0)
v3 = make_point_rvertex(0, 0, 5)
# 计算距离
dist12 = calculate_distance(v1, v2)
dist13 = calculate_distance(v1, v3)
dist23 = calculate_distance(v2, v3)
print(f"v1 到 v2 的距离: {dist12}") # 5.0
print(f"v1 到 v3 的距离: {dist13}") # 5.0
print(f"v2 到 v3 的距离: {dist23}") # 约 7.07
```
## String Representation
```python
from simplecadapi import make_point_rvertex
vertex = make_point_rvertex(1.234, 5.678, 9.012)
vertex.add_tag("test_point")
vertex.set_metadata("created_by", "example")
print(vertex)
```
Output:
```
Vertex:
coordinates: [1.234, 5.678, 9.012]
tags: [test_point]
metadata:
created_by: example
```
## Relationships with Other Geometries
Vertices are the fundamental elements that compose more complex geometries:
- **Edge**: Defined by two vertices
- **Wire**: Composed of multiple connected edges, containing multiple vertices
- **Face**: Boundary defined by vertices
- **Solid**: Ultimately composed of vertices
## Notes
- Vertex objects wrap CADQuery's underlying vertices; do not modify coordinates directly
- Tags are of string type and are case-sensitive
- Metadata can store values of any type
- Vertex coordinates are read-only; to modify positions, create new vertices
- Floating-point coordinates may have precision issues; consider tolerance when comparing
@@ -0,0 +1,452 @@
# Wire
## Overview
`Wire` is the wire class in the SimpleCAD API, representing a 1D geometric path formed by connecting multiple edges. A wire can be open (different start and end points) or closed (forming a closed path). It wraps the CADQuery Wire object and adds tagging functionality.
## Class Definition
```python
class Wire(TaggedMixin):
"""线类,包装CADQuery的Wire,添加标签功能"""
```
## Inheritance
- Inherits from `TaggedMixin`, providing tag and metadata functionality
## Usage
- Represent continuous paths or contours
- Form the boundary of faces (Face)
- Define paths for sweep, extrude, and other operations
- Create complex geometric contours
## Constructor
### `__init__(cq_wire)`
Initializes a wire object.
**Parameters:**
- `cq_wire` (cadquery.Wire): A CADQuery wire object
**Raises:**
- `ValueError`: When the input wire object is invalid
**Example:**
```python
from simplecadapi import (
make_rectangle_rwire,
make_circle_rwire,
make_polyline_rwire
)
# 通过 SimpleCAD 函数创建线
rectangle = make_rectangle_rwire(width=5, height=3)
circle = make_circle_rwire(center=(0, 0, 0), radius=2.0)
polyline = make_polyline_rwire(points=[(0, 0, 0), (1, 1, 0), (2, 0, 0)])
```
## Main Properties
- `cq_wire`: The underlying CADQuery wire object
- `_tags`: Tag set (inherited from TaggedMixin)
- `_metadata`: Metadata dictionary (inherited from TaggedMixin)
## Common Methods
### `get_edges()`
Get all edges that make up the wire.
**Returns:**
- `List[Edge]`: List of edge objects
**Raises:**
- `ValueError`: When edge list retrieval fails
**Example:**
```python
from simplecadapi import make_rectangle_rwire
rectangle = make_rectangle_rwire(width=4, height=3)
edges = rectangle.get_edges()
print(f"矩形由 {len(edges)} 条边组成")
for i, edge in enumerate(edges):
print(f"边 {i}: 长度 {edge.get_length():.3f}")
```
### `is_closed()`
Check if the wire is closed.
**Returns:**
- `bool`: Returns True if the wire is closed, False otherwise
**Raises:**
- `ValueError`: When closure check fails
**Example:**
```python
from simplecadapi import make_rectangle_rwire, make_polyline_rwire
# 闭合线
rectangle = make_rectangle_rwire(width=5, height=3)
print(f"矩形是否闭合: {rectangle.is_closed()}") # True
# 开放线
polyline = make_polyline_rwire(points=[(0, 0, 0), (1, 1, 0), (2, 0, 0)])
print(f"折线是否闭合: {polyline.is_closed()}") # False
```
### 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 Different Types of Wires
```python
from simplecadapi import (
make_rectangle_rwire,
make_circle_rwire,
make_polyline_rwire,
make_spline_rwire
)
# 矩形线
rectangle = make_rectangle_rwire(width=10, height=6)
rectangle.add_tag("rectangle")
rectangle.add_tag("closed")
# 圆形线
circle = make_circle_rwire(center=(0, 0, 0), radius=3.0)
circle.add_tag("circle")
circle.add_tag("closed")
# 折线
polyline = make_polyline_rwire(points=[
(0, 0, 0), (2, 0, 0), (2, 2, 0), (1, 3, 0), (0, 2, 0)
])
polyline.add_tag("polyline")
polyline.add_tag("open")
# 样条线
spline = make_spline_rwire(points=[
(0, 0, 0), (1, 2, 0), (3, 2, 0), (4, 0, 0)
])
spline.add_tag("spline")
spline.add_tag("smooth")
# 分析线的属性
wires = [rectangle, circle, polyline, spline]
for wire in wires:
edges = wire.get_edges()
closed = wire.is_closed()
tags = wire.get_tags()
print(f"线类型: {tags}, 边数: {len(edges)}, 闭合: {closed}")
```
### Creating Complex Contours
```python
from simplecadapi import make_polyline_rwire
def create_complex_profile():
"""创建复杂的轮廓线"""
# 定义轮廓点
points = [
(0, 0, 0), # 起点
(10, 0, 0), # 底边
(10, 2, 0), # 右下
(8, 2, 0), # 内凹1
(8, 4, 0), #
(10, 4, 0), # 右上
(10, 6, 0), # 顶边右
(0, 6, 0), # 顶边左
(0, 4, 0), # 左上
(2, 4, 0), # 内凹2
(2, 2, 0), #
(0, 2, 0), # 左下
(0, 0, 0) # 闭合回起点
]
profile = make_polyline_rwire(points=points)
profile.add_tag("complex_profile")
profile.add_tag("symmetric")
# 添加几何信息
edges = profile.get_edges()
total_length = sum(edge.get_length() for edge in edges)
profile.set_metadata("total_length", total_length)
profile.set_metadata("point_count", len(points))
profile.set_metadata("edge_count", len(edges))
return profile
profile = create_complex_profile()
print(f"复杂轮廓: {profile.get_tags()}")
print(f"总长度: {profile.get_metadata('total_length'):.3f}")
print(f"边数: {profile.get_metadata('edge_count')}")
```
### Wire Analysis and Processing
```python
from simplecadapi import make_rectangle_rwire, make_circle_rwire
def analyze_wire_properties():
"""分析线的属性"""
# 创建不同的线
rectangle = make_rectangle_rwire(width=6, height=4)
circle = make_circle_rwire(center=(0, 0, 0), radius=2.0)
wires = [rectangle, circle]
for i, wire in enumerate(wires):
# 基本属性
edges = wire.get_edges()
is_closed = wire.is_closed()
# 计算总长度
total_length = sum(edge.get_length() for edge in edges)
# 分析边
edge_lengths = [edge.get_length() for edge in edges]
min_edge_length = min(edge_lengths)
max_edge_length = max(edge_lengths)
avg_edge_length = sum(edge_lengths) / len(edge_lengths)
# 添加标签和元数据
wire.add_tag(f"wire_{i}")
wire.add_tag("analyzed")
if is_closed:
wire.add_tag("closed")
else:
wire.add_tag("open")
wire.set_metadata("total_length", total_length)
wire.set_metadata("edge_count", len(edges))
wire.set_metadata("min_edge_length", min_edge_length)
wire.set_metadata("max_edge_length", max_edge_length)
wire.set_metadata("avg_edge_length", avg_edge_length)
# 分类边
for j, edge in enumerate(edges):
edge.add_tag(f"wire_{i}_edge_{j}")
edge.set_metadata("parent_wire", i)
edge.set_metadata("position_in_wire", j)
print(f"线 {i}:")
print(f" 总长度: {total_length:.3f}")
print(f" 边数: {len(edges)}")
print(f" 闭合: {is_closed}")
print(f" 最短边: {min_edge_length:.3f}")
print(f" 最长边: {max_edge_length:.3f}")
print(f" 平均边长: {avg_edge_length:.3f}")
print()
analyze_wire_properties()
```
### Wire Transformation and Operations
```python
from simplecadapi import make_rectangle_rwire, translate_shape, rotate_shape
def transform_wires():
"""变换线的操作"""
# 创建基础矩形
base_rect = make_rectangle_rwire(width=4, height=2)
base_rect.add_tag("base")
base_rect.add_tag("original")
# 创建变换后的线
translated_rect = translate_shape(base_rect, offset=(5, 0, 0))
translated_rect.add_tag("translated")
rotated_rect = rotate_shape(base_rect, axis=(0, 0, 1), angle=45)
rotated_rect.add_tag("rotated")
# 收集所有线
all_wires = [base_rect, translated_rect, rotated_rect]
# 分析变换结果
for wire in all_wires:
edges = wire.get_edges()
total_length = sum(edge.get_length() for edge in edges)
# 计算边界框(简化版)
all_coords = []
for edge in edges:
start_coords = edge.get_start_vertex().get_coordinates()
end_coords = edge.get_end_vertex().get_coordinates()
all_coords.extend([start_coords, end_coords])
if all_coords:
min_x = min(coord[0] for coord in all_coords)
max_x = max(coord[0] for coord in all_coords)
min_y = min(coord[1] for coord in all_coords)
max_y = max(coord[1] for coord in all_coords)
wire.set_metadata("bbox_min", (min_x, min_y))
wire.set_metadata("bbox_max", (max_x, max_y))
wire.set_metadata("bbox_width", max_x - min_x)
wire.set_metadata("bbox_height", max_y - min_y)
wire.set_metadata("total_length", total_length)
print(f"线标签: {wire.get_tags()}")
print(f" 总长度: {total_length:.3f}")
if wire.get_metadata("bbox_min"):
print(f" 边界框: {wire.get_metadata('bbox_min')} 到 {wire.get_metadata('bbox_max')}")
print()
transform_wires()
```
### Building Wire Sequences
```python
from simplecadapi import make_segment_rwire
def create_wire_sequence():
"""创建线的序列"""
# 创建连续的线段
segments = []
# 定义路径点
waypoints = [
(0, 0, 0),
(2, 0, 0),
(2, 2, 0),
(0, 2, 0),
(0, 4, 0),
(4, 4, 0),
(4, 0, 0),
(6, 0, 0)
]
# 创建连续的线段
for i in range(len(waypoints) - 1):
start = waypoints[i]
end = waypoints[i + 1]
segment = make_segment_rwire(start=start, end=end)
segment.add_tag(f"segment_{i}")
segment.add_tag("path_segment")
# 添加方向信息
direction = (
end[0] - start[0],
end[1] - start[1],
end[2] - start[2]
)
if direction[0] > 0:
segment.add_tag("eastward")
elif direction[0] < 0:
segment.add_tag("westward")
if direction[1] > 0:
segment.add_tag("northward")
elif direction[1] < 0:
segment.add_tag("southward")
segment.set_metadata("start_point", start)
segment.set_metadata("end_point", end)
segment.set_metadata("direction", direction)
segment.set_metadata("sequence_index", i)
segments.append(segment)
# 分析序列
total_path_length = sum(seg.get_edges()[0].get_length() for seg in segments)
print(f"路径段数: {len(segments)}")
print(f"总路径长度: {total_path_length:.3f}")
# 按方向分类
eastward = [s for s in segments if s.has_tag("eastward")]
northward = [s for s in segments if s.has_tag("northward")]
print(f"向东段数: {len(eastward)}")
print(f"向北段数: {len(northward)}")
return segments
sequence = create_wire_sequence()
```
## String Representation
```python
from simplecadapi import make_rectangle_rwire
wire = make_rectangle_rwire(width=5, height=3)
wire.add_tag("example_rectangle")
wire.set_metadata("area", 15.0)
print(wire)
```
Output:
```
Wire:
edge_count: 4
closed: True
edges:
edge_0:
length: 5.000
vertices:
start: (0.0, 0.0, 0.0)
end: (5.0, 0.0, 0.0)
edge_1:
length: 3.000
vertices:
start: (5.0, 0.0, 0.0)
end: (5.0, 3.0, 0.0)
edge_2:
length: 5.000
vertices:
start: (5.0, 3.0, 0.0)
end: (0.0, 3.0, 0.0)
edge_3:
length: 3.000
vertices:
start: (0.0, 3.0, 0.0)
end: (0.0, 0.0, 0.0)
tags: [example_rectangle]
metadata:
area: 15.0
```
## Relationships with Other Geometry
- **Edge (Edge)**: Components of a wire
- **Face (Face)**: Closed wires can define face boundaries
- **Solid (Solid)**: Can be created by sweeping or extruding wires
## Notes
- Wire edges must be continuous; endpoints of adjacent edges must coincide
- The start and end points of a closed wire must coincide
- Wire orientation affects certain operations (such as face normal direction)
- Complex wires may contain self-intersections and require special handling
- Wire length equals the sum of all edge lengths
- When creating faces, outer boundary wires should be counterclockwise; inner boundary wires should be clockwise