File: pycsg.rst

package info (click to toggle)
ezdxf 1.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 104,528 kB
  • sloc: python: 182,341; makefile: 116; lisp: 20; ansic: 4
file content (186 lines) | stat: -rw-r--r-- 5,868 bytes parent folder | download
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