yapCAD BREP Implementation ========================== This document describes yapCAD's BREP (Boundary Representation) implementation, which provides native solid modeling through integration with OpenCascade (OCC). Overview -------- yapCAD supports two modes of solid geometry: 1. **Tessellated Mode** (default, no dependencies) - Solids are represented as triangle meshes. Suitable for visualization and STL export. 2. **BREP Mode** (requires pythonocc-core) - Solids carry native OCC BREP data with exact geometric definitions. Required for STEP import/export and high-fidelity boolean operations. Implementation Status --------------------- **Complete:** * Full OCC BREP wrapper classes (``BrepSolid``, ``BrepFace``, ``BrepEdge``, ``BrepVertex``) * BREP-aware primitives: ``box()``, ``sphere()``, ``cylinder()``, ``cone()``, ``prism()`` * Solid operations: ``extrude()``, ``tube()``, ``makeRevolutionSolid()``, ``makeLoftSolid()`` * Adaptive sweep: ``sweep_adaptive()``, ``sweep_adaptive_hollow()`` * Boolean operations via OCC when BREP data present * STEP import with topology preservation * STEP export (tessellated and analytic modes) * STL import/export * Transform propagation to BREP data **Curves (2D/3D):** * Line, arc, circle, ellipse (full and partial arcs) * Catmull-Rom splines (open and closed) * NURBS curves * Parabola, hyperbola primitives **Surfaces:** * Planar, cylindrical, conical, spherical surfaces via OCC * Tessellation on demand with quality controls * Surface evaluation and normal computation Installation ------------ BREP functionality requires pythonocc-core, installed via conda:: conda env create -f environment.yml conda activate yapcad-brep Without OCC, yapCAD operates in tessellation-only mode with reduced functionality: * No STEP import/export * Boolean operations use mesh-based algorithms (lower fidelity) * Sweep operations produce tessellated results only Architecture ------------ BREP Wrapper Classes ~~~~~~~~~~~~~~~~~~~~ Located in ``yapcad/brep.py``: * ``BrepSolid`` - Wraps OCC TopoDS_Solid with lazy tessellation * ``BrepFace`` - Surface face with trim curves * ``BrepEdge`` - Edge with curve geometry * ``BrepVertex`` - Vertex with tolerance These classes provide bidirectional conversion:: # yapCAD solid -> OCC shape from yapcad.brep import brep_from_solid occ_shape = brep_from_solid(solid).shape # OCC shape -> yapCAD solid from yapcad.brep import solid_from_brep solid = solid_from_brep(occ_shape) BREP Attachment ~~~~~~~~~~~~~~~ Solids store BREP data in metadata under ``'brep'`` key:: solid = box(10, 20, 30) brep = solid[5].get('brep') # BrepSolid instance if OCC available Boolean Operations ~~~~~~~~~~~~~~~~~~ Environment variables control engine selection: * ``YAPCAD_BOOLEAN_ENGINE`` - Force engine: ``native``, ``trimesh``, ``occ`` * ``YAPCAD_MESH_BOOLEAN_ENGINE`` - Mesh fallback when OCC unavailable Auto-selection logic: 1. If both operands have BREP data → use OCC 2. Otherwise → use mesh-based engine STEP Import/Export ------------------ Import ~~~~~~ :: from yapcad.brep import import_step # Returns yapCAD Geometry with attached BREP geometry = import_step("model.step") # Access the underlying solid solid = geometry.geom Export ~~~~~~ Two modes available: **Tessellated (default)**:: from yapcad.geom3d import write_solid_step write_solid_step(solid, "output.step") **Analytic** (preserves exact geometry):: # Via environment variable import os os.environ['YAPCAD_STEP_FORMAT'] = 'analytic' write_solid_step(solid, "output.step") # Or via write_step_analytic directly from yapcad.brep import write_step_analytic write_step_analytic(solid, "output.step") Adaptive Sweep Operations ------------------------- The ``sweep_adaptive()`` function creates solids by sweeping a profile along a path with tangent-tracking orientation:: from yapcad.geom3d_util import sweep_adaptive solid = sweep_adaptive( profile, # region2d for cross-section spine, # path3d with line/arc segments angle_threshold_deg=5.0, # threshold for new section frame_mode='minimal_twist' # or 'frenet', 'custom' ) For hollow profiles (pipes):: solid = sweep_adaptive( outer_profile, spine, inner_profiles=[inner_profile], # one or more voids angle_threshold_deg=5.0 ) Edge Treatment: Fillets and Chamfers ------------------------------------- Apply rounded (fillet) or beveled (chamfer) edges to BREP solids:: from yapcad.brep import ( fillet_all_edges, chamfer_all_edges, fillet_edges, chamfer_edges, brep_from_solid ) from yapcad.geom3d_util import box # Create a box solid my_box = box(20, 20, 10) brep_solid = brep_from_solid(my_box) # Apply fillet (rounded edges) to all edges filleted = fillet_all_edges(brep_solid, radius=2.0) # Apply chamfer (beveled edges) to all edges # Creates symmetric 45° chamfers chamfered = chamfer_all_edges(brep_solid, distance=1.5) # Apply fillet to specific edges only selected_edges = [edge1, edge2] # BrepEdge objects partial_fillet = fillet_edges(brep_solid, selected_edges, radius=1.0) # Apply chamfer to specific edges partial_chamfer = chamfer_edges(brep_solid, selected_edges, distance=1.0) **Notes:** * Requires pythonocc-core (BREP environment) * Returns a new ``BrepSolid`` instance with modified edges * Edges that cannot be filleted/chamfered (too small, geometric constraints) are skipped * For selective edge operations, use edge selection helpers (see below) * See ``examples/fillet_chamfer_demo.dsl`` for DSL usage examples Edge Selection Functions ------------------------- The ``yapcad.brep_edge_select`` module provides functions to select edges from BREP solids based on geometric criteria. This is particularly useful for applying selective fillets or chamfers to specific edges rather than all edges of a solid. Basic Selection Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~ **Select by Direction:** :: from yapcad.brep_edge_select import ( select_vertical_edges, select_horizontal_edges, select_edges_by_direction ) # Select edges parallel to Z axis (vertical edges) vertical = select_vertical_edges(brep_solid, tolerance_deg=1.0) # Select edges perpendicular to Z axis (horizontal edges) horizontal = select_horizontal_edges(brep_solid, tolerance_deg=1.0) # Select edges parallel to a custom direction edges_45deg = select_edges_by_direction(brep_solid, direction=(1, 1, 0), tolerance_deg=1.0) **Select by Z Position:** :: from yapcad.brep_edge_select import ( select_top_edges, select_bottom_edges, select_edges_at_z, select_edges_in_z_range, select_edges_crossing_z ) # Select edges at the top of the solid (maximum Z) top_edges = select_top_edges(brep_solid, tolerance=0.001) # Select edges at the bottom of the solid (minimum Z) bottom_edges = select_bottom_edges(brep_solid, tolerance=0.001) # Select edges at a specific Z height mid_edges = select_edges_at_z(brep_solid, z_value=5.0, tolerance=0.001) # Select edges within a Z range range_edges = select_edges_in_z_range(brep_solid, z_min=2.0, z_max=8.0, tolerance=0.001) # Select edges that cross a specific Z height (vertical edges spanning Z) crossing = select_edges_crossing_z(brep_solid, z_value=5.0, tolerance=0.001) **Select by Length:** :: from yapcad.brep_edge_select import select_edges_by_length # Select edges within a length range long_edges = select_edges_by_length(brep_solid, min_length=10.0) short_edges = select_edges_by_length(brep_solid, max_length=5.0) mid_edges = select_edges_by_length(brep_solid, min_length=5.0, max_length=15.0) **Select by Position:** :: from yapcad.brep_edge_select import ( select_edges_near_point, select_edges_in_cylinder ) # Select edges near a point (based on edge midpoint) near_origin = select_edges_near_point(brep_solid, target_point=(0, 0, 5), max_distance=2.0) # Select edges within a cylindrical region (useful for holes/bosses) around_hole = select_edges_in_cylinder(brep_solid, center=(10, 10, 0), radius=5.0, axis=(0, 0, 1)) Filtering Functions ~~~~~~~~~~~~~~~~~~~ :: from yapcad.brep_edge_select import ( filter_curved_edges, filter_linear_edges, get_all_edges ) # Get all edges from a solid all_edges = get_all_edges(brep_solid) # Filter to only curved (non-linear) edges curved = filter_curved_edges(all_edges) # Filter to only linear (straight) edges linear = filter_linear_edges(all_edges) Set Operations on Edge Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Combine, intersect, or subtract edge selections:: from yapcad.brep_edge_select import ( union_edges, intersect_edges, subtract_edges ) # Combine multiple selections (removes duplicates) combined = union_edges(vertical_edges, top_edges, bottom_edges) # Find edges common to all selections common = intersect_edges(horizontal_edges, long_edges, top_edges) # Remove edges from a selection all_but_bottom = subtract_edges(all_edges, bottom_edges) Edge Information ~~~~~~~~~~~~~~~~ Get detailed information about an edge:: from yapcad.brep_edge_select import edge_info info = edge_info(some_edge) # Returns dict with: # - length: Edge length # - endpoints: ((x1, y1, z1), (x2, y2, z2)) # - midpoint: (x, y, z) # - direction: (dx, dy, dz) for linear edges, None for curved # - is_linear: True if edge is straight # - is_vertical: True if parallel to Z axis # - is_horizontal: True if perpendicular to Z axis Complete Example: Selective Filleting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's a complete example showing how to apply fillets only to specific edges:: from yapcad.brep import brep_from_solid, fillet_edges from yapcad.brep_edge_select import ( select_vertical_edges, select_top_edges, intersect_edges ) from yapcad.geom3d_util import box # Create a box my_box = box(20, 20, 10) brep = brep_from_solid(my_box) # Select only the vertical edges at the top of the box vertical = select_vertical_edges(brep) top = select_top_edges(brep) top_vertical = intersect_edges(vertical, top) # Apply fillet only to these edges filleted_box = fillet_edges(brep, top_vertical, radius=1.0) **Notes:** * All selection functions require pythonocc-core * Selection functions return lists of ``BrepEdge`` objects * Edge selections can be combined using set operations * Tolerance parameters control how precisely edges must match criteria * Edge selections avoid duplicates (each unique edge appears once) Helical Extrusion ----------------- Create smooth helical extrusions (twisted features) for gears, columns, and spiral geometry:: from yapcad.geom3d_util import helical_extrude from yapcad.geom import line, point # Define a square profile centered at origin square = [ line(point(-5, -5), point(5, -5)), line(point(5, -5), point(5, 5)), line(point(5, 5), point(-5, 5)), line(point(-5, 5), point(-5, -5)) ] # Create twisted column: 20mm tall, 90° total twist twisted_column = helical_extrude( square, height=20, twist_angle_deg=90, segments=64 # Higher = smoother (recommended: 64+) ) **Parameters:** * ``profile`` - region2d (closed loop of 2D curves) centered near origin * ``height`` - Extrusion height along Z-axis * ``twist_angle_deg`` - Total rotation angle (positive = counterclockwise from +Z) * ``segments`` - Number of lofting sections (default 64, use 64+ for smooth surfaces) **Notes:** * Requires pythonocc-core (uses lofting for smooth surfaces) * Zero twist produces simple extrusion * Ideal for helical gears (use with involute gear profiles) API Reference ------------- Key Functions ~~~~~~~~~~~~~ ``yapcad.brep``: * ``import_step(path)`` - Import STEP file * ``write_step_analytic(solid, path)`` - Export analytic STEP * ``brep_from_solid(solid)`` - Extract BREP wrapper * ``solid_from_brep(shape)`` - Create solid from OCC shape * ``fillet_all_edges(brep_solid, radius)`` - Round all edges * ``chamfer_all_edges(brep_solid, distance)`` - Bevel all edges * ``fillet_edges(brep_solid, edges, radius)`` - Round specific edges * ``chamfer_edges(brep_solid, edges, distance)`` - Bevel specific edges ``yapcad.brep_edge_select``: * ``get_all_edges(brep_solid)`` - Get all unique edges as BrepEdge objects * ``select_vertical_edges(brep_solid, tolerance_deg)`` - Select edges parallel to Z * ``select_horizontal_edges(brep_solid, tolerance_deg)`` - Select edges perpendicular to Z * ``select_edges_by_direction(brep_solid, direction, tolerance_deg)`` - Select by direction * ``select_edges_by_length(brep_solid, min_length, max_length)`` - Select by length range * ``select_edges_at_z(brep_solid, z_value, tolerance)`` - Select edges at Z height * ``select_edges_in_z_range(brep_solid, z_min, z_max, tolerance)`` - Select edges in Z range * ``select_edges_crossing_z(brep_solid, z_value, tolerance)`` - Select edges spanning Z * ``select_top_edges(brep_solid, tolerance)`` - Select edges at maximum Z * ``select_bottom_edges(brep_solid, tolerance)`` - Select edges at minimum Z * ``select_edges_near_point(brep_solid, point, max_distance)`` - Select by proximity * ``select_edges_in_cylinder(brep_solid, center, radius, axis)`` - Select in cylindrical region * ``filter_curved_edges(edges)`` - Keep only curved edges * ``filter_linear_edges(edges)`` - Keep only linear edges * ``edge_info(edge)`` - Get detailed edge properties * ``union_edges(*edge_lists)`` - Combine edge lists (remove duplicates) * ``intersect_edges(*edge_lists)`` - Find common edges * ``subtract_edges(base_edges, edges_to_remove)`` - Remove edges from list ``yapcad.geom3d``: * ``box(l, w, h)`` - Rectangular prism with BREP * ``sphere(center, radius)`` - Sphere with BREP * ``cylinder(center, axis, radius, height)`` - Cylinder with BREP * ``cone(center, axis, radius1, radius2, height)`` - Cone/frustum with BREP ``yapcad.geom3d_util``: * ``extrude(profile, direction, height)`` - Linear extrusion * ``makeRevolutionSolid(profile, axis, angle)`` - Revolution * ``makeLoftSolid(profiles)`` - Loft through profiles * ``sweep_adaptive(profile, spine, ...)`` - Adaptive sweep * ``helical_extrude(profile, height, twist_angle_deg, ...)`` - Helical/twisted extrusion * ``radial_pattern_solid(solid, count, ...)`` - Circular array of solids * ``linear_pattern_solid(solid, count, spacing)`` - Linear array of solids * ``radial_pattern_surface(surf, count, ...)`` - Circular array of surfaces * ``linear_pattern_surface(surf, count, spacing)`` - Linear array of surfaces ``yapcad.geom_util``: * ``radial_pattern(geometry, count, ...)`` - Circular array of 2D geometry * ``linear_pattern(geometry, count, spacing)`` - Linear array of 2D geometry ``yapcad.text3d``: * ``text_solid(text, height, depth, ...)`` - Create 3D extruded text * ``engrave_text(target, text, position, normal, ...)`` - Engrave text into solid * ``text_width(text, height, spacing, font)`` - Calculate text width for positioning Testing ------- BREP tests are in ``tests/test_brep.py`` and ``tests/test_step_import.py``. Run with:: PYTHONPATH=./src pytest tests/test_brep.py -v Limitations ----------- * Complex surfaces (NURBS, offset) may tessellate during operations * Boolean operations on complex geometry may fail; check results * Performance depends on OCC installation quality Future Work ----------- * Improved NURBS surface support * Direct BREP editing operations * Better error reporting for failed operations * Integration with DSL for BREP-specific operations