1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
.. automodule:: ezdxf.addons.pycsg
.. _pycsg2:
PyCSG
=====
Constructive Solid Geometry (CSG) is a modeling technique that uses Boolean
operations like union and intersection to combine 3D solids. This library
implements CSG operations on meshes elegantly and concisely using BSP trees,
and is meant to serve as an easily understandable implementation of the
algorithm. All edge cases involving overlapping coplanar polygons in both
solids are correctly handled.
Example for usage:
.. code-block:: Python
import ezdxf
from ezdxf.render.forms import cube, cylinder_2p
from ezdxf.addons.pycsg import CSG
# create new DXF document
doc = ezdxf.new()
msp = doc.modelspace()
# create same geometric primitives as MeshTransformer() objects
cube1 = cube()
cylinder1 = cylinder_2p(count=32, base_center=(0, -1, 0), top_center=(0, 1, 0), radius=.25)
# build solid union
union = CSG(cube1) + CSG(cylinder1)
# convert to mesh and render mesh to modelspace
union.mesh().render_mesh(msp, dxfattribs={'color': 1})
# build solid difference
difference = CSG(cube1) - CSG(cylinder1)
# convert to mesh, translate mesh and render mesh to modelspace
difference.mesh().translate(1.5).render_mesh(msp, dxfattribs={'color': 3})
# build solid intersection
intersection = CSG(cube1) * CSG(cylinder1)
# convert to mesh, translate mesh and render mesh to modelspace
intersection.mesh().translate(2.75).render_mesh(msp, dxfattribs={'color': 5})
doc.saveas('csg.dxf')
.. image:: gfx/pycsg01.png
:alt: Cube vs Cylinder
This CSG kernel supports only meshes as :class:`~ezdxf.render.MeshBuilder` objects, which can be created from and
converted to DXF :class:`~ezdxf.entities.Mesh` entities.
This CSG kernel is **not** compatible with ACIS objects like :class:`~ezdxf.entities.Solid3d`,
:class:`~ezdxf.entities.Body`, :class:`~ezdxf.entities.Surface` or :class:`~ezdxf.entities.Region`.
.. note::
This is a pure Python implementation, don't expect great performance and the implementation is based on an
unbalanced `BSP tree`_, so in the case of :class:`RecursionError`, increase the recursion limit:
.. code-block:: Python
import sys
actual_limit = sys.getrecursionlimit()
# default is 1000, increasing too much may cause a seg fault
sys.setrecursionlimit(10000)
... # do the CSG stuff
sys.setrecursionlimit(actual_limit)
CSG works also with spheres, but with really bad runtime behavior and most likely :class:`RecursionError`
exceptions, and use `quadrilaterals`_ as body faces to reduce face count by setting
argument `quads` to ``True``.
.. code-block:: Python
import ezdxf
from ezdxf.render.forms import sphere, cube
from ezdxf.addons.pycsg import CSG
doc = ezdxf.new()
doc.set_modelspace_vport(6, center=(5, 0))
msp = doc.modelspace()
cube1 = cube().translate(-.5, -.5, -.5)
sphere1 = sphere(count=32, stacks=16, radius=.5, quads=True)
union = (CSG(cube1) + CSG(sphere1)).mesh()
union.render_mesh(msp, dxfattribs={'color': 1})
subtract = (CSG(cube1) - CSG(sphere1)).mesh().translate(2.5)
subtract.render_mesh(msp, dxfattribs={'color': 3})
intersection = (CSG(cube1) * CSG(sphere1)).mesh().translate(4)
intersection.render_mesh(msp, dxfattribs={'color': 5})
.. image:: gfx/pycsg02.png
:alt: Cube vs Sphere
Hard Core CSG - Menger Sponge Level 3 vs Sphere
Required runtime on an old Xeon E5-1620 Workstation @ 3.60GHz (2020), with default
recursion limit of 1000 on Windows 10:
- CPython 3.8.1 64bit: ~60 seconds,
- PyPy [PyPy 7.2.0] 32bit: ~6 seconds, and using ``__slots__`` reduced runtime
below 5 seconds, yes - PyPy is worth a look for long running scripts!
Updated runtime in 2024 on an i7-12700K @ 3.60GHz (peak ~5GHz), Windows 11:
- CPython 3.11.6 64bit: ~3.4 seconds
- PyPy 3.9.18 [PyPy 7.3.13] 64bit: ~1.5 seconds
.. code-block:: Python
from ezdxf.render.forms import sphere
from ezdxf.addons import MengerSponge
from ezdxf.addons.pycsg import CSG
doc = ezdxf.new()
doc.layers.new('sponge', dxfattribs={'color': 5})
doc.layers.new('sphere', dxfattribs={'color': 6})
doc.set_modelspace_vport(6, center=(5, 0))
msp = doc.modelspace()
sponge1 = MengerSponge(level=3).mesh()
sphere1 = sphere(count=32, stacks=16, radius=.5, quads=True).translate(.25, .25, 1)
subtract = (CSG(sponge1, meshid=1) - CSG(sphere1, meshid=2))
# get mesh result by id
subtract.mesh(1).render_mesh(msp, dxfattribs={'layer': 'sponge'})
subtract.mesh(2).render_mesh(msp, dxfattribs={'layer': 'sphere'})
.. image:: gfx/menger_sponge_vs_sphere_level_3.png
:alt: Menger Sponge vs Sphere
CSG Class
---------
.. autoclass:: CSG(mesh: MeshBuilder, meshid: int = 0)
.. automethod:: mesh
.. automethod:: union
.. method:: __add__
.. code-block:: Python
union = A + B
.. automethod:: subtract
.. method:: __sub__
.. code-block:: Python
difference = A - B
.. automethod:: intersect
.. method:: __mul__
.. code-block:: Python
intersection = A * B
.. automethod:: inverse
License
-------
- Original implementation `csg.js`_, Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license.
- Python port `pycsg`_, Copyright (c) 2012 Tim Knip (http://www.floorplanner.com), under the MIT license.
- Additions by Alex Pletzer (Pennsylvania State University)
- Integration as `ezdxf` add-on, Copyright (c) 2020, Manfred Moitzi, MIT License.
.. _csg.js: https://github.com/evanw/csg.js
.. _pycsg: https://github.com/timknip/pycsg
.. _BSP tree: https://en.wikipedia.org/wiki/Binary_space_partitioning
.. _quadrilaterals: https://en.wikipedia.org/wiki/Quadrilateral
|