├── .gitignore
├── .travis.yml
├── AUTHORS.md
├── LICENSE
├── OCCUtils
├── Common.py
├── Construct.py
├── Image.py
├── Iteration.py
├── Topology.py
├── __init__.py
├── base.py
├── edge.py
├── face.py
├── shell.py
├── solid.py
├── types_lut.py
├── vertex.py
└── wire.py
├── README.md
├── doc
└── README
├── examples
├── README
├── curve_geom_plate.igs
├── occutils_geomplate.py
└── occutils_surfaces.py
├── setup.py
└── test
└── occutils_test.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
59 | # VScode
60 | .vscode/launch.json
61 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: cpp
4 |
5 | matrix:
6 | include:
7 | - env: PYTHON="3.6" CONDA_PY=36
8 | os: linux
9 | dist: bionic
10 | - env: PYTHON="3.6" CONDA_PY=36
11 | os: osx
12 | osx_image: xcode9.4
13 | - env: PYTHON="3.7" CONDA_PY=37
14 | os: linux
15 | dist: bionic
16 | - env: PYTHON="3.7" CONDA_PY=37
17 | os: osx
18 | osx_image: xcode9.4
19 |
20 |
21 | install:
22 | - if [ ${PYTHON:0:1} == "2" ]; then
23 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then
24 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh;
25 | else
26 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-MacOSX-x86_64.sh -O miniconda.sh;
27 | fi;
28 | else
29 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then
30 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
31 | else
32 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh;
33 | fi;
34 | fi;
35 | - chmod +x miniconda.sh
36 | # When we are installing the 32 Bit conda on a 64 Bit system, the miniconda
37 | # installer will ask for a "yes" despite the -b flag, so we pipe in a yes
38 | - yes | ./miniconda.sh -b -p $HOME/miniconda
39 | #- bash miniconda.sh -b -p -f $HOME/miniconda
40 | - export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH"
41 | - hash -r
42 | - conda config --set always_yes yes --set changeps1 no
43 | # Useful for debugging any issues with conda
44 | - conda info -a
45 | # osx needs a dedicate environment
46 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
47 | conda create -n pythonocc-utils-test;
48 | source activate pythonocc-utils-test;
49 | fi;
50 | - conda install -c dlr-sc -c pythonocc pythonocc-core=7.4.0rc1
51 |
52 | script:
53 | # osx needs a dedicate environment
54 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
55 | source activate pythonocc-utils-test;
56 | fi;
57 | - python setup.py build install
58 | - cd test
59 | - python occutils_test.py
60 |
61 | branches:
62 | only:
63 | - master
64 | - /^review/
65 |
--------------------------------------------------------------------------------
/AUTHORS.md:
--------------------------------------------------------------------------------
1 | The OCCUtils contains code contributed by (alphabetical sort):
2 |
3 | - Adam Lange (https://github.com/adamLange)
4 |
5 | - Jelle Feringa (orginal author, https://github.com/jf---)
6 |
7 | - Thomas Paviot (https://github.com/tpaviot)
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/OCCUtils/Common.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 |
3 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | import random
21 |
22 | from OCC.Core.Bnd import Bnd_Box
23 | from OCC.Core.BRepBndLib import brepbndlib_Add
24 | from OCC.Core.TColgp import (
25 | TColgp_HArray1OfPnt,
26 | TColgp_Array1OfPnt,
27 | TColgp_Array1OfPnt2d,
28 | TColgp_Array1OfVec,
29 | )
30 | from OCC.Core.TColStd import TColStd_HArray1OfBoolean
31 | from OCC.Core.BRepAdaptor import (
32 | BRepAdaptor_Curve,
33 | BRepAdaptor_Curve,
34 | BRepAdaptor_CompCurve,
35 | BRepAdaptor_CompCurve,
36 | )
37 | from OCC.Core.GeomAPI import (
38 | GeomAPI_Interpolate,
39 | GeomAPI_PointsToBSpline,
40 | GeomAPI_ProjectPointOnCurve,
41 | )
42 | from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Trsf
43 | from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform
44 | from OCC.Core.TopoDS import TopoDS_Edge, TopoDS_Shape, TopoDS_Wire, TopoDS_Vertex
45 | from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB
46 | from OCC.Core.GProp import GProp_GProps
47 | from OCC.Core.GeomAbs import GeomAbs_C1, GeomAbs_C2, GeomAbs_C3
48 | from OCC.Core.BRepGProp import (
49 | brepgprop_LinearProperties,
50 | brepgprop_SurfaceProperties,
51 | brepgprop_VolumeProperties,
52 | )
53 | from OCC.Core.GeomAdaptor import GeomAdaptor_Curve
54 | from OCC.Core.Geom import Geom_Curve
55 |
56 | from OCC.Core import Graphic3d
57 |
58 | # ===========================================================================
59 | # No PythonOCC dependencies...
60 | # ===========================================================================
61 |
62 |
63 | class assert_isdone(object):
64 | """
65 | raises an assertion error when IsDone() returns false, with the error
66 | specified in error_statement
67 | """
68 |
69 | def __init__(self, to_check, error_statement):
70 | self.to_check = to_check
71 | self.error_statement = error_statement
72 |
73 | def __enter__(
74 | self,
75 | ):
76 | if self.to_check.IsDone():
77 | pass
78 | else:
79 | raise AssertionError(self.error_statement)
80 |
81 | def __exit__(self, assertion_type, value, traceback):
82 | pass
83 |
84 |
85 | def roundlist(li, n_decimals=3):
86 | return [round(i, n_decimals) for i in li]
87 |
88 |
89 | # ===========================================================================
90 | # CONSTANTS
91 | # ===========================================================================
92 |
93 | TOLERANCE = 1e-6
94 |
95 |
96 | def get_boundingbox(shape, tol=TOLERANCE):
97 | """
98 | :param shape: TopoDS_Shape such as TopoDS_Face
99 | :param tol: tolerance
100 | :return: xmin, ymin, zmin, xmax, ymax, zmax
101 | """
102 | bbox = Bnd_Box()
103 | bbox.SetGap(tol)
104 | brepbndlib_Add(shape, bbox)
105 | xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
106 | return xmin, ymin, zmin, xmax, ymax, zmax
107 |
108 |
109 | def smooth_pnts(pnts):
110 | smooth = [pnts[0]]
111 | for i in range(1, len(pnts) - 1):
112 | prev = pnts[i - 1]
113 | this = pnts[i]
114 | next_pnt = pnts[i + 1]
115 | pt = (prev + this + next_pnt) / 3.0
116 | smooth.append(pt)
117 | smooth.append(pnts[-1])
118 | return smooth
119 |
120 |
121 | # ===========================================================================
122 | # Data type utilities
123 | # ===========================================================================
124 |
125 |
126 | def color(r, g, b):
127 | return Quantity_Color(r, g, b, Quantity_TOC_RGB)
128 |
129 |
130 | def to_string(_string):
131 | from OCC.Core.TCollection import TCollection_ExtendedString
132 |
133 | return TCollection_ExtendedString(_string)
134 |
135 |
136 | def to_tcol_(_list, collection_type):
137 | array = collection_type(1, len(_list) + 1)
138 | for n, i in enumerate(_list):
139 | array.SetValue(n + 1, i)
140 | return array
141 |
142 |
143 | def _Tcol_dim_1(li, _type):
144 | """function factory for 1-dimensional TCol* types"""
145 | pts = _type(0, len(li) - 1)
146 | for n, i in enumerate(li):
147 | pts.SetValue(n, i)
148 | pts.thisown = False
149 | return pts
150 |
151 |
152 | def point_list_to_TColgp_Array1OfPnt(li):
153 | pts = TColgp_Array1OfPnt(0, len(li) - 1)
154 | for n, i in enumerate(li):
155 | pts.SetValue(n, i)
156 | return pts
157 |
158 |
159 | def point2d_list_to_TColgp_Array1OfPnt2d(li):
160 | return _Tcol_dim_1(li, TColgp_Array1OfPnt2d)
161 |
162 |
163 | # ===========================================================================
164 | # --- INTERPOLATION ---
165 | # ===========================================================================
166 |
167 |
168 | def filter_points_by_distance(list_of_point, distance=0.1):
169 | """
170 | get rid of those point that lie within tolerance of a
171 | consequtive series of points
172 | """
173 | tmp = [list_of_point[0]]
174 | for a in list_of_point[1:]:
175 | if any([a.IsEqual(i, distance) for i in tmp]):
176 | continue
177 | else:
178 | tmp.append(a)
179 | return tmp
180 |
181 |
182 | def points_to_bspline(pnts):
183 | """
184 | Points to bspline
185 | """
186 | pnts = point_list_to_TColgp_Array1OfPnt(pnts)
187 | crv = GeomAPI_PointsToBSpline(pnts)
188 | return crv.Curve()
189 |
190 |
191 | def interpolate_points_to_spline(
192 | list_of_points, start_tangent, end_tangent, filter_pts=True, tolerance=TOLERANCE
193 | ):
194 | """
195 | GeomAPI_Interpolate is buggy: need to use `fix` in order
196 | to get the right points in...
197 | """
198 |
199 | def fix(li, _type):
200 | """function factory for 1-dimensional TCol* types"""
201 | pts = _type(1, len(li))
202 | for n, i in enumerate(li):
203 | pts.SetValue(n + 1, i)
204 | pts.thisown = False
205 | return pts
206 |
207 | if filter_pts:
208 | list_of_points = filter_points_by_distance(list_of_points, 0.1)
209 |
210 | fixed_points = fix(list_of_points, TColgp_HArray1OfPnt)
211 | try:
212 | interp = GeomAPI_Interpolate(fixed_points, False, tolerance)
213 | interp.Load(start_tangent, end_tangent, False)
214 | interp.Perform()
215 | if interp.IsDone():
216 | return interp.Curve()
217 | except RuntimeError:
218 | print("Failed to interpolate the shown points")
219 |
220 |
221 | def interpolate_points_vectors_to_spline(
222 | list_of_points, list_of_vectors, vector_mask=None, tolerance=TOLERANCE
223 | ):
224 | """
225 | build a curve from a set of points and vectors
226 | the vectors describe the tangent vector at the corresponding point
227 | """
228 | # GeomAPI_Interpolate is buggy: need to use `fix` in order to
229 | # get the right points in...
230 | assert len(list_of_points) == len(
231 | list_of_vectors
232 | ), "vector and point list not of same length"
233 |
234 | def fix(li, _type):
235 | """function factory for 1-dimensional TCol* types"""
236 | pts = _type(1, len(li))
237 | for n, i in enumerate(li):
238 | pts.SetValue(n + 1, i)
239 | pts.thisown = False
240 | return pts
241 |
242 | if vector_mask is not None:
243 | assert len(vector_mask) == len(
244 | list_of_points
245 | ), "length vector mask is not of length points list nor []"
246 | else:
247 | vector_mask = [True for i in range(len(list_of_points))]
248 |
249 | fixed_mask = fix(vector_mask, TColStd_HArray1OfBoolean)
250 | fixed_points = fix(list_of_points, TColgp_HArray1OfPnt)
251 | fixed_vectors = fix(list_of_vectors, TColgp_Array1OfVec)
252 |
253 | try:
254 | interp = GeomAPI_Interpolate(fixed_points, False, tolerance)
255 | interp.Load(fixed_vectors, fixed_mask, False)
256 | interp.Perform()
257 | if interp.IsDone():
258 | return interp.Curve()
259 | except RuntimeError:
260 | # the exception was unclear
261 | raise RuntimeError("FAILED TO INTERPOLATE THE POINTS")
262 |
263 |
264 | def interpolate_points_to_spline_no_tangency(
265 | list_of_points, filter_pts=True, closed=False, tolerance=TOLERANCE
266 | ):
267 | """
268 | GeomAPI_Interpolate is buggy: need to use `fix`
269 | in order to get the right points in...
270 | """
271 |
272 | def fix(li, _type):
273 | """function factory for 1-dimensional TCol* types"""
274 | pts = _type(1, len(li))
275 | for n, i in enumerate(li):
276 | pts.SetValue(n + 1, i)
277 | pts.thisown = False
278 | return pts
279 |
280 | if filter_pts:
281 | list_of_points = filter_points_by_distance(list_of_points, 0.1)
282 |
283 | fixed_points = fix(list_of_points, TColgp_HArray1OfPnt)
284 | try:
285 | interp = GeomAPI_Interpolate(fixed_points, closed, tolerance)
286 | interp.Perform()
287 | if interp.IsDone():
288 | return interp.Curve()
289 |
290 | except RuntimeError:
291 | # the exception was unclear
292 | raise RuntimeError("FAILED TO INTERPOLATE THE POINTS")
293 |
294 |
295 | # ===========================================================================
296 | # --- RANDOMNESS ---
297 | # ===========================================================================
298 |
299 |
300 | def random_vec():
301 | x, y, z = [random.uniform(-1, 1) for i in range(3)]
302 | return gp_Vec(x, y, z)
303 |
304 |
305 | def random_colored_material_aspect():
306 | clrs = [i for i in dir(Graphic3d) if i.startswith("Graphic3d_NOM_")]
307 | color = random.sample(clrs, 1)[0]
308 | print("color", color)
309 | return Graphic3d.Graphic3d_MaterialAspect(getattr(Graphic3d, color))
310 |
311 |
312 | def random_color():
313 | return color(random.random(), random.random(), random.random())
314 |
315 |
316 | # ===========================================================================
317 | # --- BUILD PATCHES ---
318 | # ===========================================================================
319 |
320 |
321 | def common_vertex(edg1, edg2):
322 | from OCC.Core.TopExp import topexp_CommonVertex
323 |
324 | vert = TopoDS_Vertex()
325 | if topexp_CommonVertex(edg1, edg2, vert):
326 | return vert
327 | else:
328 | raise ValueError("no common vertex found")
329 |
330 |
331 | def midpoint(pntA, pntB):
332 | """
333 | computes the point that lies in the middle between pntA and pntB
334 | @param pntA: gp_Pnt
335 | @param pntB: gp_Pnt
336 | """
337 | vec1 = gp_Vec(pntA.XYZ())
338 | vec2 = gp_Vec(pntB.XYZ())
339 | veccie = (vec1 + vec2) / 2.0
340 | return gp_Pnt(veccie.XYZ())
341 |
342 |
343 | def center_boundingbox(shape):
344 | """
345 | compute the center point of a TopoDS_Shape, based on its bounding box
346 | @param shape: TopoDS_* instance
347 | returns a gp_Pnt instance
348 | """
349 | xmin, ymin, zmin, xmax, ymax, zmax = get_boundingbox(shape, 1e-6)
350 | return midpoint(gp_Pnt(xmin, ymin, zmin), gp_Pnt(xmax, ymax, zmax))
351 |
352 |
353 | def point_in_boundingbox(solid, pnt, tolerance=1e-5):
354 | """returns True if *pnt* lies in *boundingbox*, False if not
355 | this is a much speedier test than checking the TopoDS_Solid
356 | Args:
357 | solid TopoDS_Solid
358 | pnt: gp_Pnt
359 |
360 | Returns: bool
361 | """
362 | bbox = Bnd_Box()
363 | bbox.SetGap(tolerance)
364 | brepbndlib_Add(solid, bbox)
365 | return not bbox.IsOut(pnt)
366 |
367 |
368 | def point_in_solid(solid, pnt, tolerance=1e-5):
369 | """returns True if *pnt* lies in *solid*, False if not
370 | Args:
371 | solid TopoDS_Solid
372 | pnt: gp_Pnt
373 |
374 | Returns: bool
375 | """
376 | from OCC.Core.BRepClass3d import BRepClass3d_SolidClassifier
377 | from OCC.Core.TopAbs import TopAbs_ON, TopAbs_OUT, TopAbs_IN
378 |
379 | _in_solid = BRepClass3d_SolidClassifier(solid, pnt, tolerance)
380 | print("State", _in_solid.State())
381 | if _in_solid.State() == TopAbs_ON:
382 | return None, "on"
383 | if _in_solid.State() == TopAbs_OUT:
384 | return False, "out"
385 | if _in_solid.State() == TopAbs_IN:
386 | return True, "in"
387 |
388 |
389 | def intersection_from_three_planes(planeA, planeB, planeC):
390 | """
391 | intersection from 3 planes
392 | accepts both Geom_Plane and gp_Pln
393 | @param planeA:
394 | @param planeB:
395 | @param planeC:
396 | @param show:
397 | """
398 | from OCC.Core.IntAna import IntAna_Int3Pln
399 |
400 | planeA = planeA if not hasattr(planeA, "Pln") else planeA.Pln()
401 | planeB = planeB if not hasattr(planeB, "Pln") else planeB.Pln()
402 | planeC = planeC if not hasattr(planeC, "Pln") else planeC.Pln()
403 |
404 | intersection_planes = IntAna_Int3Pln(planeA, planeB, planeC)
405 | pnt = intersection_planes.Value()
406 | return pnt
407 |
408 |
409 | def intersect_shape_by_line(
410 | topods_shape, line, low_parameter=0.0, hi_parameter=float("+inf")
411 | ):
412 | """
413 | finds the intersection of a shape and a line
414 |
415 | :param shape: any TopoDS_*
416 | :param line: gp_Lin
417 | :param low_parameter:
418 | :param hi_parameter:
419 |
420 | :return: a list with a number of tuples that corresponds to the number
421 | of intersections found
422 | the tuple contains ( gp_Pnt, TopoDS_Face, u,v,w ), respectively the
423 | intersection point, the intersecting face
424 | and the u,v,w parameters of the intersection point
425 | :raise:
426 | """
427 | from OCC.Core.IntCurvesFace import IntCurvesFace_ShapeIntersector
428 |
429 | shape_inter = IntCurvesFace_ShapeIntersector()
430 | shape_inter.Load(topods_shape, TOLERANCE)
431 | shape_inter.PerformNearest(line, low_parameter, hi_parameter)
432 |
433 | with assert_isdone(shape_inter, "failed to computer shape / line intersection"):
434 | return (
435 | shape_inter.Pnt(1),
436 | shape_inter.Face(1),
437 | shape_inter.UParameter(1),
438 | shape_inter.VParameter(1),
439 | shape_inter.WParameter(1),
440 | )
441 |
442 |
443 | def normal_vector_from_plane(plane, vec_length=1.0):
444 | """
445 | returns a vector normal to the plane of length vec_length
446 | @param plane:
447 | """
448 | trns = gp_Vec(plane.Axis().Direction())
449 | return trns.Normalized() * vec_length
450 |
451 |
452 | # ===========================================================================
453 | # FIX
454 | # ===========================================================================
455 |
456 |
457 | def fix_tolerance(shape, tolerance=TOLERANCE):
458 | from OCC.Core.ShapeFix import ShapeFix_ShapeTolerance
459 |
460 | ShapeFix_ShapeTolerance().SetTolerance(shape, tolerance)
461 |
462 |
463 | def fix_continuity(edge, continuity=1):
464 | from OCC.Core.ShapeUpgrade import ShapeUpgrade_ShapeDivideContinuity
465 |
466 | su = ShapeUpgrade_ShapeDivideContinuity(edge)
467 | su.SetBoundaryCriterion(eval("GeomAbs_C" + str(continuity)))
468 | su.Perform()
469 | te = st(su.Result())
470 | return te
471 |
472 |
473 | def resample_curve_with_uniform_deflection(
474 | curve,
475 | deflection=0.5,
476 | degreeMin=3,
477 | degreeMax=8,
478 | continuity=GeomAbs_C2,
479 | tolerance=1e-4,
480 | ):
481 | """
482 | fits a bspline through the samples on `curve`
483 | @param curve: TopoDS_Wire, TopoDS_Edge, curve
484 | @param n_samples:
485 | """
486 | from OCC.Core.GCPnts import GCPnts_UniformDeflection
487 |
488 | crv = to_adaptor_3d(curve)
489 | defl = GCPnts_UniformDeflection(crv, deflection)
490 | with assert_isdone(defl, "failed to compute UniformDeflection"):
491 | print("Number of points:", defl.NbPoints())
492 | sampled_pnts = [defl.Value(i) for i in range(1, defl.NbPoints())]
493 | resampled_curve = GeomAPI_PointsToBSpline(
494 | point_list_to_TColgp_Array1OfPnt(sampled_pnts),
495 | degreeMin,
496 | degreeMax,
497 | continuity,
498 | tolerance,
499 | )
500 | return resampled_curve.Curve().GetObject()
501 |
502 |
503 | # ===========================================================================
504 | # global properties
505 | # ===========================================================================
506 |
507 |
508 | class GpropsFromShape(object):
509 | def __init__(self, shape, tolerance=1e-5):
510 | self.shape = shape
511 | self.tolerance = tolerance
512 |
513 | def volume(self):
514 | """returns the volume of a solid"""
515 | prop = GProp_GProps()
516 | brepgprop_VolumeProperties(self.shape, prop, self.tolerance)
517 | return prop
518 |
519 | def surface(self):
520 | """returns the area of a surface"""
521 | prop = GProp_GProps()
522 | brepgprop_SurfaceProperties(self.shape, prop, self.tolerance)
523 | return prop
524 |
525 | def linear(self):
526 | """returns the length of a wire or edge"""
527 | prop = GProp_GProps()
528 | brepgprop_LinearProperties(self.shape, prop)
529 | return prop
530 |
531 |
532 | def curve_length(crv):
533 | """
534 | get the length from a TopoDS_Edge or TopoDS_Wire
535 | """
536 | assert isinstance(crv, (TopoDS_Wire, TopoDS_Edge)), "either a wire or edge..."
537 | gprop = GpropsFromShape(crv)
538 | return gprop.linear().Mass()
539 |
540 |
541 | # =======================================================================
542 | # Distance
543 | # =======================================================================
544 |
545 |
546 | def minimum_distance(shp1, shp2):
547 | """
548 | compute minimum distance between 2 BREP's
549 | @param shp1: any TopoDS_*
550 | @param shp2: any TopoDS_*
551 |
552 | @return: minimum distance,
553 | minimum distance points on shp1
554 | minimum distance points on shp2
555 | """
556 | from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape
557 |
558 | bdss = BRepExtrema_DistShapeShape(shp1, shp2)
559 | bdss.Perform()
560 | with assert_isdone(bdss, "failed computing minimum distances"):
561 | min_dist = bdss.Value()
562 | min_dist_shp1, min_dist_shp2 = [], []
563 | for i in range(1, bdss.NbSolution() + 1):
564 | min_dist_shp1.append(bdss.PointOnShape1(i))
565 | min_dist_shp2.append(bdss.PointOnShape2(i))
566 | return min_dist, min_dist_shp1, min_dist_shp2
567 |
568 |
569 | def vertex2pnt(vertex):
570 | """returns a gp_Pnt from a TopoDS_Vertex"""
571 | from OCC.Core.Core.BRep import BRep_Tool
572 |
573 | return BRep_Tool.Pnt(vertex)
574 |
575 |
576 | def adapt_edge_to_curve(edg):
577 | """
578 | returns a curve adaptor from an edge
579 | @param edg: TopoDS_Edge
580 | """
581 | return BRepAdaptor_Curve(edg)
582 |
583 |
584 | def adapt_edge_to_hcurve(edg):
585 | c = BRepAdaptor_Curve()
586 | c.ChangeCurve().Initialize(edg)
587 | return c
588 |
589 |
590 | def to_adaptor_3d(curveType):
591 | """
592 | abstract curve like type into an adaptor3d
593 | @param curveType:
594 | """
595 | if isinstance(curveType, TopoDS_Wire):
596 | return BRepAdaptor_CompCurve(curveType)
597 | elif isinstance(curveType, TopoDS_Edge):
598 | return BRepAdaptor_Curve(curveType)
599 | elif issubclass(curveType.__class__, Geom_Curve):
600 | return GeomAdaptor_Curve(curveType)
601 | elif hasattr(curveType, "GetObject"):
602 | _crv = curveType.GetObject()
603 | if issubclass(_crv.__class__, Geom_Curve):
604 | return GeomAdaptor_Curve(curveType)
605 | else:
606 | raise TypeError(
607 | "allowed types are Wire, Edge or a subclass of Geom_Curve\nGot a %s"
608 | % (curveType.__class__)
609 | )
610 |
611 |
612 | def project_point_on_curve(crv, pnt):
613 | if isinstance(crv, TopoDS_Shape):
614 | # get the curve
615 | crv = adapt_edge_to_curve(crv).Curve().Curve()
616 | else:
617 | raise NotImplementedError("expected a TopoDS_Edge...")
618 | rrr = GeomAPI_ProjectPointOnCurve(pnt, crv)
619 | return rrr.LowerDistanceParameter(), rrr.NearestPoint()
620 |
621 |
622 | def project_point_on_plane(plane, point):
623 | """
624 | project point on plane
625 | @param plane: Geom_Plane
626 | @param point: gp_Pnt
627 | """
628 | from OCC.Core.ProjLib import projlib_Project
629 |
630 | pl = plane.Pln()
631 | aa, bb = projlib_Project(pl, point).Coord()
632 | point = plane.Value(aa, bb)
633 | return point
634 |
635 |
636 | def wire_to_curve(
637 | wire, tolerance=TOLERANCE, order=GeomAbs_C2, max_segment=200, max_order=12
638 | ):
639 | """
640 | a wire can consist of many edges.
641 | these edges are merged given a tolerance and a curve
642 | @param wire:
643 | """
644 | adap = BRepAdaptor_CompCurve(wire)
645 | hadap = BRepAdaptor_CompCurve(adap)
646 | from OCC.Core.Approx import Approx_Curve3d
647 |
648 | approx = Approx_Curve3d(hadap, tolerance, order, max_segment, max_order)
649 | with assert_isdone(approx, "not able to compute approximation from wire"):
650 | return approx.Curve().GetObject()
651 |
652 |
653 | def adapt_edge_to_curve(edg):
654 | """
655 | returns a curve adaptor from an edge
656 | @param edg: TopoDS_Edge
657 | """
658 | return BRepAdaptor_Curve(edg)
659 |
660 |
661 | def adapt_edge_to_hcurve(edg):
662 | c = BRepAdaptor_Curve()
663 | c.ChangeCurve().Initialize(edg)
664 | return c
665 |
--------------------------------------------------------------------------------
/OCCUtils/Construct.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2011-2015 Jelle Feringa (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | """
21 | This modules makes the construction of geometry a little easier
22 | """
23 |
24 | from __future__ import with_statement
25 | from functools import wraps
26 | import warnings
27 | import operator
28 | import math
29 |
30 | from OCC.Core.BRep import BRep_Tool
31 | from OCC.Core.BRepAdaptor import BRepAdaptor_Curve
32 | from OCC.Core.BRepOffset import BRepOffset_Skin
33 | from OCC.Core.Geom import Geom_TrimmedCurve
34 | from OCC.Core.GeomConvert import GeomConvert_ApproxCurve
35 | from OCC.Core.GeomLProp import GeomLProp_SLProps
36 | from OCC.Core.BRepBuilderAPI import (
37 | BRepBuilderAPI_MakeFace,
38 | BRepBuilderAPI_Transform,
39 | BRepBuilderAPI_Sewing,
40 | BRepBuilderAPI_MakePolygon,
41 | BRepBuilderAPI_MakeWire,
42 | BRepBuilderAPI_MakeSolid,
43 | BRepBuilderAPI_MakeShell,
44 | BRepBuilderAPI_MakeEdge2d,
45 | BRepBuilderAPI_MakeEdge,
46 | BRepBuilderAPI_MakeVertex,
47 | BRepBuilderAPI_FindPlane,
48 | )
49 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakePrism
50 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakeEvolved
51 | from OCC.Core.GeomAbs import (
52 | GeomAbs_Arc,
53 | GeomAbs_C2,
54 | GeomAbs_C0,
55 | GeomAbs_Tangent,
56 | GeomAbs_Intersection,
57 | GeomAbs_G1,
58 | GeomAbs_G2,
59 | GeomAbs_C1,
60 | )
61 | from OCC.Core.TopAbs import TopAbs_REVERSED
62 | from OCC.Core.TopoDS import (
63 | TopoDS_Wire,
64 | TopoDS_Solid,
65 | TopoDS_Vertex,
66 | TopoDS_Shape,
67 | TopoDS_Builder,
68 | TopoDS_Compound,
69 | )
70 | from OCC.Core.TColgp import TColgp_SequenceOfVec, TColgp_HArray1OfPnt
71 | from OCC.Core.gp import (
72 | gp_Vec,
73 | gp_Pnt,
74 | gp_Dir,
75 | gp_Trsf,
76 | gp_Ax1,
77 | gp_Quaternion,
78 | gp_Circ,
79 | gp_Pln,
80 | )
81 |
82 | from OCCUtils.Common import (
83 | TOLERANCE,
84 | assert_isdone,
85 | to_tcol_,
86 | to_adaptor_3d,
87 | vertex2pnt,
88 | smooth_pnts,
89 | points_to_bspline,
90 | project_point_on_curve,
91 | )
92 | from OCCUtils.types_lut import ShapeToTopology
93 | from OCCUtils.Topology import Topo
94 |
95 |
96 | EPSILON = TOLERANCE = 1e-6
97 | ST = ShapeToTopology()
98 |
99 |
100 | def point_to_vector(self):
101 | return gp_Vec(self.XYZ())
102 |
103 |
104 | def vector_to_point(self):
105 | return gp_Pnt(self.XYZ())
106 |
107 |
108 | def dir_to_vec(self):
109 | return gp_Vec(self)
110 |
111 |
112 | def vec_to_dir(self):
113 | return gp_Dir(self)
114 |
115 |
116 | def add_vector_to_point(self, vec):
117 | return (self.as_vec() + vec).as_pnt()
118 |
119 |
120 | def gp_Pnt_get_state(self):
121 | """pack as a tuple
122 |
123 | used for copying / serializing the instance
124 | """
125 | return self.XYZ().Coord()
126 |
127 |
128 | def gp_Pnt_set_state(self, state):
129 | """unpack tuple and return instance...
130 |
131 | used for copying / serializing the instance
132 | """
133 | self.__init__(*state)
134 |
135 |
136 | def gp_Pnt_equal(self, other):
137 | return self.IsEqual(other, TOLERANCE)
138 |
139 |
140 | def gp_pnt_print(self):
141 | x = self.X()
142 | y = self.Y()
143 | z = self.Z()
144 | return "< gp_Pnt: {0}, {1}, {2} >".format(x, y, z)
145 |
146 |
147 | def gp_vec_print(self):
148 | x = self.X()
149 | y = self.Y()
150 | z = self.Z()
151 | magn = self.Magnitude()
152 | return "< gp_Vec: {0}, {1}, {2}, magnitude: {3} >".format(x, y, z, magn)
153 |
154 |
155 | def gp_ax1_print(self):
156 | pX, pY, pZ = self.Location().Coord()
157 | dX, dY, dZ = self.Direction().Coord()
158 | return "< gp_Ax1: location: {pX}, {pY}, {pZ}, direction: {dX}, {dY}, {dZ} >".format(
159 | **vars()
160 | )
161 |
162 |
163 | def gp_trsf_print(self):
164 | _f = lambda x: [self.Value(x, i) for i in range(1, 5)]
165 | a, b, c, d = _f(1)
166 | e, f, g, h = _f(2)
167 | i, j, k, l = _f(3)
168 | return "< gp_Trsf:\n {a:.3f}, {b:.3f}, {c:.3f}, {d:.3f}\n {e:.3f}, {f:.3f}, {g:.3f}, {h:.3f}\n {i:.3f}, {j:.3f}, {k:.3f}, {l:.3f} >".format(
169 | **vars()
170 | )
171 |
172 |
173 | def gp_quat_print(self):
174 | w, x, y, z = self.W(), self.X(), self.Y(), self.Z()
175 | vec = gp_Vec()
176 | angle = math.degrees(self.GetVectorAndAngle(vec))
177 | return "< gp_Quaternion: w:{w}, x:{x}, y:{y}, z:{z} >\nvector:{vec} angle:{angle}".format(
178 | **vars()
179 | )
180 |
181 |
182 | def _apply(pnt, other, _operator):
183 | if isinstance(other, gp_Pnt):
184 | return gp_Pnt(*map(lambda x: _operator(*x), zip(pnt.Coord(), other.Coord())))
185 | else:
186 | return gp_Pnt(*map(lambda x: _operator(x, other), pnt.Coord()))
187 |
188 |
189 | def gp_pnt_add(self, other):
190 | return _apply(self, other, operator.add)
191 |
192 |
193 | def gp_pnt_sub(self, other):
194 | return _apply(self, other, operator.sub)
195 |
196 |
197 | def gp_pnt_mul(self, other):
198 | return _apply(self, other, operator.mul)
199 |
200 |
201 | def gp_pnt_div(self, other):
202 | return _apply(self, other, operator.div)
203 |
204 |
205 | # easier conversion between data types
206 | gp_Vec.as_pnt = vector_to_point
207 | gp_Pnt.as_vec = point_to_vector
208 | gp_Pnt.add_vec = add_vector_to_point
209 | gp_Dir.as_vec = dir_to_vec
210 | gp_Vec.as_dir = vec_to_dir
211 | # for copying / serializing
212 | gp_Pnt.__getstate__ = gp_Pnt_get_state
213 | gp_Pnt.__setstate__ = gp_Pnt_set_state
214 | gp_Vec.__getstate__ = gp_Pnt_get_state
215 | gp_Vec.__setstate__ = gp_Pnt_set_state
216 | # equality, not identity comparison
217 | gp_Pnt.__eq__ = gp_Pnt_equal
218 | # print gp_Pnt() should return something informative...
219 | gp_Vec.__repr__ = gp_vec_print
220 | gp_Vec.__str__ = gp_vec_print
221 | gp_Pnt.__repr__ = gp_pnt_print
222 | gp_Pnt.__str__ = gp_pnt_print
223 | gp_Ax1.__repr__ = gp_ax1_print
224 | gp_Ax1.__str__ = gp_ax1_print
225 | gp_Trsf.__repr__ = gp_trsf_print
226 | gp_Trsf.__str__ = gp_trsf_print
227 | gp_Quaternion.__repr__ = gp_quat_print
228 | gp_Quaternion.__str__ = gp_quat_print
229 | # gp_Pnt.__eq__ = gp_equal
230 | gp_Pnt.__add__ = gp_pnt_add
231 | gp_Pnt.__sub__ = gp_pnt_sub
232 | gp_Pnt.__mul__ = gp_pnt_mul
233 | gp_Pnt.__div__ = gp_pnt_div
234 |
235 | # ===========================================================================
236 | # ---TOPOLOGY---
237 | # ===========================================================================
238 |
239 |
240 | @wraps(BRepBuilderAPI_MakeSolid)
241 | def make_solid(*args):
242 | sld = BRepBuilderAPI_MakeSolid(*args)
243 | with assert_isdone(sld, "failed to produce solid"):
244 | result = sld.Solid()
245 | return result
246 |
247 |
248 | @wraps(BRepBuilderAPI_MakeShell)
249 | def make_shell(*args):
250 | shell = BRepBuilderAPI_MakeShell(*args)
251 | st = ShapeToTopology()
252 | with assert_isdone(shell, "failed to produce shell"):
253 | result = shell.Shell()
254 | return st(result)
255 |
256 |
257 | @wraps(BRepBuilderAPI_MakeFace)
258 | def make_face(*args):
259 | face = BRepBuilderAPI_MakeFace(*args)
260 | with assert_isdone(face, "failed to produce face"):
261 | result = face.Face()
262 | return result
263 |
264 |
265 | @wraps(BRepBuilderAPI_MakeEdge2d)
266 | def make_edge2d(*args):
267 | edge = BRepBuilderAPI_MakeEdge2d(*args)
268 | with assert_isdone(edge, "failed to produce edge"):
269 | result = edge.Edge()
270 | return result
271 |
272 |
273 | @wraps(BRepBuilderAPI_MakeEdge)
274 | def make_edge(*args):
275 | edge = BRepBuilderAPI_MakeEdge(*args)
276 | with assert_isdone(edge, "failed to produce edge"):
277 | result = edge.Edge()
278 | return result
279 |
280 |
281 | @wraps(BRepBuilderAPI_MakeVertex)
282 | def make_vertex(*args):
283 | vert = BRepBuilderAPI_MakeVertex(*args)
284 | with assert_isdone(vert, "failed to produce vertex"):
285 | result = vert.Vertex()
286 | return result
287 |
288 |
289 | @wraps(BRepBuilderAPI_MakeWire)
290 | def make_wire(*args):
291 | # if we get an iterable, than add all edges to wire builder
292 | if isinstance(args[0], list) or isinstance(args[0], tuple):
293 | wire = BRepBuilderAPI_MakeWire()
294 | for i in args[0]:
295 | wire.Add(i)
296 | wire.Build()
297 | return wire.Wire()
298 |
299 | wire = BRepBuilderAPI_MakeWire(*args)
300 | wire.Build()
301 | with assert_isdone(wire, "failed to produce wire"):
302 | result = wire.Wire()
303 | return result
304 |
305 |
306 | @wraps(BRepBuilderAPI_MakePolygon)
307 | def make_polygon(args, closed=False):
308 | poly = BRepBuilderAPI_MakePolygon()
309 | for pt in args:
310 | # support nested lists
311 | if isinstance(pt, list) or isinstance(pt, tuple):
312 | for i in pt:
313 | poly.Add(i)
314 | else:
315 | poly.Add(pt)
316 | if closed:
317 | poly.Close()
318 | poly.Build()
319 |
320 | with assert_isdone(poly, "failed to produce wire"):
321 | result = poly.Wire()
322 | return result
323 |
324 |
325 | @wraps(BRepBuilderAPI_MakePolygon)
326 | def make_closed_polygon(*args):
327 | poly = BRepBuilderAPI_MakePolygon()
328 | for pt in args:
329 | if isinstance(pt, list) or isinstance(pt, tuple):
330 | for i in pt:
331 | poly.Add(i)
332 | else:
333 | poly.Add(pt)
334 | poly.Build()
335 | poly.Close()
336 | with assert_isdone(poly, "failed to produce wire"):
337 | result = poly.Wire()
338 | return result
339 |
340 |
341 | # ===========================================================================
342 | # PRIMITIVES
343 | # ===========================================================================
344 |
345 |
346 | def make_circle(pnt, radius):
347 | """
348 | returns an edge
349 | @param pnt:
350 | @param radius:
351 | """
352 | circ = gp_Circ()
353 | circ.SetLocation(pnt)
354 | circ.SetRadius(radius)
355 | return make_edge(circ)
356 |
357 |
358 | def make_line(pnt1, pnt2):
359 | return make_edge(pnt1, pnt2)
360 |
361 |
362 | def make_evolved(spine, profile):
363 | evol = BRepOffsetAPI_MakeEvolved(spine, profile)
364 | with assert_isdone(evol, "failed buillding evolved"):
365 | evol.Build()
366 | return evol.Evolved()
367 |
368 |
369 | def make_pipe(spine, profile):
370 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakePipe
371 |
372 | pipe = BRepOffsetAPI_MakePipe(spine, profile)
373 | with assert_isdone(pipe, "failed building pipe"):
374 | pipe.Build()
375 | return pipe.Shape()
376 |
377 |
378 | def make_prism(profile, vec):
379 | """
380 | makes a finite prism
381 | """
382 | pri = BRepPrimAPI_MakePrism(profile, vec, True)
383 | with assert_isdone(pri, "failed building prism"):
384 | pri.Build()
385 | return pri.Shape()
386 |
387 |
388 | def make_offset_shape(
389 | shapeToOffset,
390 | offsetDistance,
391 | tolerance=TOLERANCE,
392 | offsetMode=BRepOffset_Skin,
393 | intersection=False,
394 | selfintersection=False,
395 | joinType=GeomAbs_Arc,
396 | ):
397 | """
398 | builds an offsetted shell from a shape
399 | construct an offsetted version of the shape
400 | """
401 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakeOffsetShape
402 |
403 | try:
404 | offset = BRepOffsetAPI_MakeOffsetShape(
405 | shapeToOffset,
406 | offsetDistance,
407 | tolerance,
408 | offsetMode,
409 | intersection,
410 | selfintersection,
411 | joinType,
412 | )
413 | if offset.IsDone():
414 | return offset.Shape()
415 | else:
416 | return None
417 | except RuntimeError("failed to offset shape"):
418 | return None
419 |
420 |
421 | def make_offset(wire_or_face, offsetDistance, altitude=0, joinType=GeomAbs_Arc):
422 | """
423 | builds a offsetted wire or face from a wire or face
424 | construct an offsetted version of the shape
425 |
426 | @param wire_or_face: the wire or face to offset
427 | @param offsetDistance: the distance to offset
428 | @param altitude: move the offsetted shape to altitude
429 | from the normal of the wire or face
430 | @param joinType: the type of offset you want
431 | can be one of GeomAbs_Arc, GeomAbs_Tangent, GeomAbs_Intersection
432 |
433 | note: a shape that has a negative offsetDistance will return
434 | a sharp corner
435 | """
436 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakeOffset
437 |
438 | _joints = [GeomAbs_Arc, GeomAbs_Tangent, GeomAbs_Intersection]
439 | assert joinType in _joints, "%s is not one of %s" % (joinType, _joints)
440 | try:
441 | offset = BRepOffsetAPI_MakeOffset(wire_or_face, joinType)
442 | offset.Perform(offsetDistance, altitude)
443 | if offset.IsDone():
444 | return ST(offset.Shape())
445 | else:
446 | return None
447 | except RuntimeError("failed to offset shape"):
448 | return None
449 |
450 |
451 | def make_loft(
452 | elements,
453 | ruled=False,
454 | tolerance=TOLERANCE,
455 | continuity=GeomAbs_C2,
456 | check_compatibility=True,
457 | ):
458 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_ThruSections
459 |
460 | sections = BRepOffsetAPI_ThruSections(False, ruled, tolerance)
461 | for i in elements:
462 | if isinstance(i, TopoDS_Wire):
463 | sections.AddWire(i)
464 | elif isinstance(i, TopoDS_Vertex):
465 | sections.AddVertex(i)
466 | else:
467 | raise TypeError(
468 | "elements is a list of TopoDS_Wire or TopoDS_Vertex, found a %s fool"
469 | % i.__class__
470 | )
471 |
472 | sections.CheckCompatibility(check_compatibility)
473 | sections.SetContinuity(continuity)
474 | sections.Build()
475 | with assert_isdone(sections, "failed lofting"):
476 | te = ShapeToTopology()
477 | loft = te(sections.Shape())
478 | return loft
479 |
480 |
481 | def make_ruled(edgeA, edgeB):
482 | from OCC.Core.BRepFill import brepfill_Face
483 |
484 | return brepfill_Face(edgeA, edgeB)
485 |
486 |
487 | def make_plane(
488 | center=gp_Pnt(0, 0, 0),
489 | vec_normal=gp_Vec(0, 0, 1),
490 | extent_x_min=-100.0,
491 | extent_x_max=100.0,
492 | extent_y_min=-100.0,
493 | extent_y_max=100.0,
494 | depth=0.0,
495 | ):
496 | if depth != 0:
497 | center = center.add_vec(gp_Vec(0, 0, depth))
498 | PL = gp_Pln(center, vec_normal.as_dir())
499 | face = make_face(PL, extent_x_min, extent_x_max, extent_y_min, extent_y_max)
500 | return face
501 |
502 |
503 | def make_oriented_box(v_corner, v_x, v_y, v_z):
504 | """
505 | produces an oriented box
506 | oriented meaning here that the x,y,z axis do not have to be
507 | cartesian aligned
508 |
509 | :param v_corner: the lower corner
510 | :param v_x: gp_Vec that describes the X-axis
511 | :param v_y: gp_Vec that describes the Y-axis
512 | :param v_z: gp_Vec that describes the Z-axis
513 | :return: TopoDS_Solid
514 | """
515 | from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakePipe
516 |
517 | verts = map(
518 | lambda x: x.as_pnt(),
519 | [v_corner, v_corner + v_x, v_corner + v_x + v_y, v_corner + v_y],
520 | )
521 | p = make_polygon(verts, closed=True)
522 | li = make_line(v_corner.as_pnt(), (v_corner + v_z).as_pnt())
523 | bmp = BRepOffsetAPI_MakePipe(p, li)
524 | bmp.Build()
525 | shp = bmp.Shape()
526 |
527 | bottom = make_face(p)
528 | top = translate_topods_from_vector(bottom, v_z, True)
529 | oriented_bbox = make_solid(sew_shapes([bottom, shp, top]))
530 | return oriented_bbox
531 |
532 |
533 | @wraps(BRepPrimAPI_MakeBox)
534 | def make_box(*args):
535 | box = BRepPrimAPI_MakeBox(*args)
536 | box.Build()
537 | with assert_isdone(box, "failed to built a cube..."):
538 | return box.Shape()
539 |
540 |
541 | def make_n_sided(edges, points, continuity=GeomAbs_C0):
542 | """
543 | builds an n-sided patch, respecting the constraints defined by *edges*
544 | and *points*
545 |
546 | a simplified call to the BRepFill_Filling class
547 | its simplified in the sense that to all constraining edges and points
548 | the same level of *continuity* will be applied
549 |
550 | *continuity* represents:
551 |
552 | GeomAbs_C0 : the surface has to pass by 3D representation of the edge
553 | GeomAbs_G1 : the surface has to pass by 3D representation of the edge
554 | and to respect tangency with the given face
555 | GeomAbs_G2 : the surface has to pass by 3D representation of the edge
556 | and to respect tangency and curvature with the given face.
557 |
558 | NOTE: it is not required to set constraining points.
559 | just leave the tuple or list empty
560 |
561 | :param edges: the constraining edges
562 | :param points: the constraining points
563 | :param continuity: GeomAbs_0, 1, 2
564 | :return: TopoDS_Face
565 | """
566 | from OCC.Core.BRepFill import BRepFill_Filling
567 |
568 | n_sided = BRepFill_Filling()
569 | for edg in edges:
570 | n_sided.Add(edg, continuity)
571 | for pt in points:
572 | n_sided.Add(pt)
573 | n_sided.Build()
574 | face = n_sided.Face()
575 | return face
576 |
577 |
578 | def make_n_sections(edges):
579 | from OCC.Core.TopTools import TopTools_SequenceOfShape
580 | from OCC.Core.BRepFill import BRepFill_NSections
581 |
582 | seq = TopTools_SequenceOfShape()
583 | for i in edges:
584 | seq.Append(i)
585 | n_sec = BRepFill_NSections(seq)
586 | return n_sec
587 |
588 |
589 | def make_coons(edges):
590 | from OCC.GeomFill import GeomFill_BSplineCurves, GeomFill_StretchStyle
591 |
592 | if len(edges) == 4:
593 | spl1, spl2, spl3, spl4 = edges
594 | srf = GeomFill_BSplineCurves(spl1, spl2, spl3, spl4, GeomFill_StretchStyle)
595 | elif len(edges) == 3:
596 | spl1, spl2, spl3 = edges
597 | srf = GeomFill_BSplineCurves(spl1, spl2, spl3, GeomFill_StretchStyle)
598 | elif len(edges) == 2:
599 | spl1, spl2 = edges
600 | srf = GeomFill_BSplineCurves(spl1, spl2, GeomFill_StretchStyle)
601 | else:
602 | raise ValueError("give 2,3 or 4 curves")
603 | return srf.Surface()
604 |
605 |
606 | def make_constrained_surface_from_edges(edges):
607 | """
608 | DOESNT RESPECT BOUNDARIES
609 | """
610 | from OCC.GeomPlate import GeomPlate_MakeApprox, GeomPlate_BuildPlateSurface
611 | from OCC.Core.BRepFill import BRepFill_CurveConstraint
612 |
613 | bpSrf = GeomPlate_BuildPlateSurface(3, 15, 2)
614 | for edg in edges:
615 | c = BRepAdaptor_Curve()
616 | c.ChangeCurve().Initialize(edg)
617 | constraint = BRepFill_CurveConstraint(c, 0)
618 | bpSrf.Add(constraint)
619 | bpSrf.Perform()
620 | maxSeg, maxDeg, critOrder = 9, 8, 0
621 | tol = 1e-4
622 | srf = bpSrf.Surface()
623 | plate = GeomPlate_MakeApprox(srf, tol, maxSeg, maxDeg, tol, critOrder)
624 | uMin, uMax, vMin, vMax = srf.Bounds()
625 | face = make_face(plate.Surface(), uMin, uMax, vMin, vMax)
626 | return face
627 |
628 |
629 | def add_wire_to_face(face, wire, reverse=False):
630 | """
631 | apply a wire to a face
632 | use reverse to set the orientation of the wire to opposite
633 | @param face:
634 | @param wire:
635 | @param reverse:
636 | """
637 | face = BRepBuilderAPI_MakeFace(face)
638 | if reverse:
639 | wire.Reverse()
640 | face.Add(wire)
641 | result = face.Face()
642 | return result
643 |
644 |
645 | def sew_shapes(shapes, tolerance=0.001):
646 | sew = BRepBuilderAPI_Sewing(tolerance)
647 | for shp in shapes:
648 | if isinstance(shp, list):
649 | for i in shp:
650 | sew.Add(i)
651 | else:
652 | sew.Add(shp)
653 | sew.Perform()
654 | print("n degenerated shapes", sew.NbDegeneratedShapes())
655 | print("n deleted faces:", sew.NbDeletedFaces())
656 | print("n free edges", sew.NbFreeEdges())
657 | print("n multiple edges:", sew.NbMultipleEdges())
658 | result = ShapeToTopology()(sew.SewedShape())
659 | return result
660 |
661 |
662 | # ===========================================================================
663 | # ---BOOL---
664 | # ===========================================================================
665 |
666 |
667 | def boolean_cut(shapeToCutFrom, cuttingShape):
668 | from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
669 |
670 | try:
671 | cut = BRepAlgoAPI_Cut(shapeToCutFrom, cuttingShape)
672 | print("Can work?", cut.BuilderCanWork())
673 | _error = {
674 | 0: "- Ok",
675 | 1: "- The Object is created but Nothing is Done",
676 | 2: "- Null source shapes is not allowed",
677 | 3: "- Check types of the arguments",
678 | 4: "- Can not allocate memory for the DSFiller",
679 | 5: "- The Builder can not work with such types of arguments",
680 | 6: "- Unknown operation is not allowed",
681 | 7: "- Can not allocate memory for the Builder",
682 | }
683 | print("Error status:", _error[cut.ErrorStatus()])
684 | cut.RefineEdges()
685 | cut.FuseEdges()
686 | shp = cut.Shape()
687 | cut.Destroy()
688 | return shp
689 | except:
690 | print("Failed to boolean cut")
691 | return shapeToCutFrom
692 |
693 |
694 | def boolean_fuse(shapeToCutFrom, joiningShape):
695 | from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse
696 |
697 | join = BRepAlgoAPI_Fuse(shapeToCutFrom, joiningShape)
698 | join.RefineEdges()
699 | join.FuseEdges()
700 | shape = join.Shape()
701 | join.Destroy()
702 | return shape
703 |
704 |
705 | def trim_wire(wire, shapeLimit1, shapeLimit2, periodic=False):
706 | """return the trimmed wire that lies between `shapeLimit1`
707 | and `shapeLimit2`
708 | returns TopoDS_Edge
709 | """
710 | adap = to_adaptor_3d(wire)
711 | bspl = adap.BSpline()
712 | if periodic:
713 | if bspl.IsClosed():
714 | bspl.SetPeriodic()
715 | else:
716 | warnings.warn(
717 | "the wire to be trimmed is not closed, hence cannot be made periodic"
718 | )
719 | p1 = project_point_on_curve(bspl, shapeLimit1)[0]
720 | p2 = project_point_on_curve(bspl, shapeLimit2)[0]
721 | a, b = sorted([p1, p2])
722 | tr = Geom_TrimmedCurve(bspl, a, b)
723 | return make_edge(tr)
724 |
725 |
726 | # ===========================================================================
727 | # ---FIXES---
728 | # ===========================================================================
729 |
730 |
731 | def fix_shape(shp, tolerance=1e-3):
732 | from OCC.ShapeFix import ShapeFix_Shape
733 |
734 | fix = ShapeFix_Shape(shp)
735 | fix.SetFixFreeShellMode(True)
736 | sf = fix.FixShellTool()
737 | sf.SetFixOrientationMode(True)
738 | fix.LimitTolerance(tolerance)
739 | fix.Perform()
740 | return fix.Shape()
741 |
742 |
743 | def fix_face(shp, tolerance=1e-3):
744 | from OCC.ShapeFix import ShapeFix_Face
745 |
746 | fix = ShapeFix_Face(shp)
747 | fix.SetMaxTolerance(tolerance)
748 | fix.Perform()
749 | return fix.Face()
750 |
751 |
752 | # ===========================================================================
753 | # --- TRANSFORM ---
754 | # ===========================================================================
755 |
756 |
757 | def translate_topods_from_vector(brep_or_iterable, vec, copy=False):
758 | """
759 | translate a brep over a vector
760 | @param brep: the Topo_DS to translate
761 | @param vec: the vector defining the translation
762 | @param copy: copies to brep if True
763 | """
764 | st = ShapeToTopology()
765 | trns = gp_Trsf()
766 | trns.SetTranslation(vec)
767 | if issubclass(brep_or_iterable.__class__, TopoDS_Shape):
768 | brep_trns = BRepBuilderAPI_Transform(brep_or_iterable, trns, copy)
769 | brep_trns.Build()
770 | return st(brep_trns.Shape())
771 | else:
772 | return [
773 | translate_topods_from_vector(brep_or_iterable, vec, copy)
774 | for i in brep_or_iterable
775 | ]
776 |
777 |
778 | def scale_uniformal(brep, pnt, factor, copy=False):
779 | """
780 | translate a brep over a vector
781 | @param brep: the Topo_DS to translate
782 | @param pnt: a gp_Pnt
783 | @param triple: scaling factor
784 | @param copy: copies to brep if True
785 | """
786 | trns = gp_Trsf()
787 | trns.SetScale(pnt, factor)
788 | brep_trns = BRepBuilderAPI_Transform(brep, trns, copy)
789 | brep_trns.Build()
790 | return brep_trns.Shape()
791 |
792 |
793 | def mirror_pnt_dir(brep, pnt, direction, copy=False):
794 | """
795 | @param brep:
796 | @param line:
797 | """
798 | trns = gp_Trsf()
799 | trns.SetMirror(gp_Ax1(pnt, direction))
800 | brep_trns = BRepBuilderAPI_Transform(brep, trns, copy)
801 | with assert_isdone(brep_trns, "could not produce mirror"):
802 | brep_trns.Build()
803 | return brep_trns.Shape()
804 |
805 |
806 | def mirror_axe2(brep, axe2, copy=False):
807 | """
808 | @param brep:
809 | @param line:
810 | """
811 | trns = gp_Trsf()
812 | trns.SetMirror(axe2)
813 | brep_trns = BRepBuilderAPI_Transform(brep, trns, copy)
814 | with assert_isdone(brep_trns, "could not produce mirror"):
815 | brep_trns.Build()
816 | return brep_trns.Shape()
817 |
818 |
819 | def rotate(brep, axe, degree, copy=False):
820 | """
821 | @param brep:
822 | @param axe:
823 | @param degree:
824 | """
825 | from math import radians
826 |
827 | trns = gp_Trsf()
828 | trns.SetRotation(axe, radians(degree))
829 | brep_trns = BRepBuilderAPI_Transform(brep, trns, copy)
830 | with assert_isdone(brep_trns, "could not produce rotation"):
831 | brep_trns.Build()
832 | return ST(brep_trns.Shape())
833 |
834 |
835 | # =============================================================================
836 | # Not so sure where this should be located
837 | # =============================================================================
838 |
839 |
840 | def face_normal(face):
841 | from OCC.Core.BRepTools import breptools_UVBounds
842 |
843 | umin, umax, vmin, vmax = breptools_UVBounds(face)
844 | surf = BRep_Tool().Surface(face)
845 | props = GeomLProp_SLProps(
846 | surf, (umin + umax) / 2.0, (vmin + vmax) / 2.0, 1, TOLERANCE
847 | )
848 | norm = props.Normal()
849 | if face.Orientation() == TopAbs_REVERSED:
850 | norm.Reverse()
851 | return norm
852 |
853 |
854 | def face_from_plane(_geom_plane, lowerLimit=-1000, upperLimit=1000):
855 | from OCC.Geom import Geom_RectangularTrimmedSurface
856 |
857 | _trim_plane = make_face(
858 | Geom_RectangularTrimmedSurface(
859 | _geom_plane, lowerLimit, upperLimit, lowerLimit, upperLimit
860 | )
861 | )
862 | return _trim_plane
863 |
864 |
865 | def find_plane_from_shape(shape, tolerance=-1):
866 | try:
867 | fpl = BRepBuilderAPI_FindPlane(shape, tolerance)
868 | if fpl.Found():
869 | return fpl.Plane()
870 | else:
871 | return None
872 | except:
873 | raise AssertionError("couldnt find plane in %s" % (shape))
874 |
875 |
876 | def fit_plane_through_face_vertices(_face):
877 | """
878 | :param _face: OCC.KBE.face.Face instance
879 | :return: Geom_Plane
880 | """
881 | from OCC.GeomPlate import GeomPlate_BuildAveragePlane
882 |
883 | uvs_from_vertices = [
884 | _face.project_vertex(vertex2pnt(i)) for i in Topo(_face).vertices()
885 | ]
886 | normals = [gp_Vec(_face.DiffGeom.normal(*uv[0])) for uv in uvs_from_vertices]
887 | points = [i[1] for i in uvs_from_vertices]
888 |
889 | NORMALS = TColgp_SequenceOfVec()
890 | [NORMALS.Append(i) for i in normals]
891 | POINTS = to_tcol_(points, TColgp_HArray1OfPnt)
892 |
893 | pl = GeomPlate_BuildAveragePlane(NORMALS, POINTS).Plane()
894 | vec = gp_Vec(pl.Location(), _face.GlobalProperties.centre())
895 | pt = (pl.Location().as_vec() + vec).as_pnt()
896 | pl.SetLocation(pt)
897 | return pl
898 |
899 |
900 | def project_edge_onto_plane(edg, plane):
901 | """
902 | :param edg: kbe.edge.Edge
903 | :param plane: Geom_Plane
904 | :return: TopoDS_Edge projected on the plane
905 | """
906 | from OCC.GeomProjLib import geomprojlib_ProjectOnPlane
907 |
908 | proj = geomprojlib_ProjectOnPlane(
909 | edg.adaptor.Curve().Curve(), plane, plane.Axis().Direction(), 1
910 | )
911 | return make_edge(proj)
912 |
913 |
914 | def curve_to_bspline(
915 | crv, tolerance=TOLERANCE, continuity=GeomAbs_C1, sections=300, degree=12
916 | ):
917 | approx_curve = GeomConvert_ApproxCurve(crv, tolerance, continuity, sections, degree)
918 | with assert_isdone(approx_curve, "could not compute bspline from curve"):
919 | return approx_curve.Curve()
920 |
921 |
922 | def compound(topo):
923 | """
924 | accumulate a bunch of TopoDS_* in list `topo` to a TopoDS_Compound
925 | @param topo: list of TopoDS_* instances
926 | """
927 | bd = TopoDS_Builder()
928 | comp = TopoDS_Compound()
929 | bd.MakeCompound(comp)
930 | for i in topo:
931 | bd.Add(comp, i)
932 | return comp
933 |
934 |
935 | def geodesic_path(
936 | pntA, pntB, edgA, edgB, kbe_face, n_segments=20, _tolerance=0.1, n_iter=20
937 | ):
938 | """
939 | :param pntA: point to start from
940 | :param pntB: point to move towards
941 | :param edgA: edge to start from
942 | :param edgB: edge to move towards
943 | :param kbe_face: kbe.face.Face on which `edgA` and `edgB` lie
944 | :param n_segments: the number of segments the geodesic is built from
945 | :param _tolerance: tolerance when the geodesic is converged
946 | :param n_iter: maximum number of iterations
947 | :return: TopoDS_Edge
948 | """
949 | uvA, srf_pnt_A = kbe_face.project_vertex(pntA)
950 | uvB, srf_pnt_B = kbe_face.project_vertex(pntB)
951 |
952 | path = []
953 | for i in range(n_segments):
954 | t = i / float(n_segments)
955 | u = uvA[0] + t * (uvB[0] - uvA[0])
956 | v = uvA[1] + t * (uvB[1] - uvA[1])
957 | path.append(kbe_face.parameter_to_point(u, v))
958 |
959 | project_pnts = lambda x: [kbe_face.project_vertex(i)[1] for i in x]
960 | poly_length = lambda x: sum(
961 | [x[i].Distance(x[i + 1]) for i in range(len(x) - 1)]
962 | ) / len(x)
963 |
964 | length = poly_length(path)
965 |
966 | n = 0
967 | while True:
968 | path = smooth_pnts(path)
969 | path = project_pnts(path)
970 | newlength = poly_length(path)
971 | if abs(newlength - length) < _tolerance or n == n_iter:
972 | crv = points_to_bspline(path)
973 | return make_edge(crv)
974 | n += 1
975 |
--------------------------------------------------------------------------------
/OCCUtils/Image.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2008-2015 Thomas Paviot (tpaviot@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | import os
21 | import os.path
22 |
23 |
24 | class Texture(object):
25 | """
26 | This class encapsulates the necessary texture properties:
27 | Filename, toScaleU, etc.
28 | """
29 |
30 | def __init__(self, filename):
31 | if not os.path.isfile(filename):
32 | raise IOError("File %s not found.\n" % filename)
33 | self._filename = filename
34 | self._toScaleU = 1.0
35 | self._toScaleV = 1.0
36 | self._toRepeatU = 1.0
37 | self._toRepeatV = 1.0
38 | self._originU = 0.0
39 | self._originV = 0.0
40 |
41 | def TextureScale(self, toScaleU, toScaleV):
42 | self._toScaleU = toScaleU
43 | self._toScaleV = toScaleV
44 |
45 | def TextureRepeat(self, toRepeatU, toRepeatV):
46 | self._toRepeatU = toRepeatU
47 | self._toRepeatV = toRepeatV
48 |
49 | def TextureOrigin(self, originU, originV):
50 | self._originU = originU
51 | self._originV = originV
52 |
53 | def GetProperties(self):
54 | return (
55 | self._filename,
56 | self._toScaleU,
57 | self._toScaleV,
58 | self._toRepeatU,
59 | self._toRepeatV,
60 | self._originU,
61 | self._originV,
62 | )
63 |
--------------------------------------------------------------------------------
/OCCUtils/Iteration.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see .
17 |
18 | """
19 | This module helps looping through topology
20 | """
21 | from OCC.Core.BRep import BRep_Tool
22 |
23 | from OCCUtils.Topology import WireExplorer, Topo
24 | from OCCUtils.edge import Edge
25 |
26 |
27 | class EdgePairsFromWire(object):
28 | """
29 | helper class to loop through a wire and return ordered pairs of edges
30 | """
31 |
32 | def __init__(self, wire):
33 | self.wire = wire
34 | self.edge_pairs = []
35 | self.prev_edge = None
36 | self.we = WireExplorer(self.wire).ordered_edges()
37 | self.number_of_edges = self.we.__length_hint__()
38 | self.previous_edge = None
39 | self.current_edge = None
40 | self.first_edge = None
41 | self.index = 0
42 |
43 | def next(self):
44 | if self.index == 0:
45 | # first edge, need to set self.previous_edge
46 | self.previous_edge = next(self.we)
47 | self.current_edge = next(self.we)
48 | self.first_edge = self.previous_edge # for the last iteration
49 | self.index += 1
50 | return [self.previous_edge, self.current_edge]
51 | elif self.index == self.number_of_edges - 1:
52 | # no next edge
53 | self.index += 1
54 | return [self.current_edge, self.first_edge]
55 | else:
56 | self.previous_edge = self.current_edge
57 | self.current_edge = next(self.we)
58 | self.index += 1
59 | return [self.previous_edge, self.current_edge]
60 |
61 | def __iter__(self):
62 | return self
63 |
64 |
65 | class LoopWirePairs(object):
66 | """
67 | for looping through consequtive wires
68 | assures that the returned edge pairs are ordered
69 | """
70 |
71 | def __init__(self, wireA, wireB):
72 | self.wireA = wireA
73 | self.wireB = wireB
74 | self.we_A = WireExplorer(self.wireA)
75 | self.we_B = WireExplorer(self.wireB)
76 | self.tp_A = Topo(self.wireA)
77 | self.tp_B = Topo(self.wireB)
78 | self.bt = BRep_Tool()
79 | self.vertsA = [v for v in self.we_A.ordered_vertices()]
80 | self.vertsB = [v for v in self.we_B.ordered_vertices()]
81 |
82 | self.edgesA = [v for v in WireExplorer(wireA).ordered_edges()]
83 | self.edgesB = [v for v in WireExplorer(wireB).ordered_edges()]
84 |
85 | self.pntsB = [self.bt.Pnt(v) for v in self.vertsB]
86 | self.number_of_vertices = len(self.vertsA)
87 | self.index = 0
88 |
89 | def closest_point(self, vertexFromWireA):
90 | pt = self.bt.Pnt(vertexFromWireA)
91 | distances = [pt.Distance(i) for i in self.pntsB]
92 | indx_max_dist = distances.index(min(distances))
93 | return self.vertsB[indx_max_dist]
94 |
95 | def next(self):
96 | if self.index == self.number_of_vertices:
97 | raise StopIteration
98 |
99 | vert = self.vertsA[self.index]
100 | closest = self.closest_point(vert)
101 | edges_a = self.tp_A.edges_from_vertex(vert)
102 | edges_b = self.tp_B.edges_from_vertex(closest)
103 | a1, a2 = Edge(next(edges_a)), Edge(next(edges_a))
104 | b1, b2 = Edge(next(edges_b)), Edge(next(edges_b))
105 | mpA = a1.mid_point()
106 | self.index += 1
107 |
108 | if mpA.Distance(b1.mid_point()) < mpA.Distance(b2.mid_point()):
109 | return iter([a1, a2]), iter([b1, b2])
110 | else:
111 | return iter([a1, a2]), iter([b2, b1])
112 |
113 | def __iter__(self):
114 | return self
115 |
--------------------------------------------------------------------------------
/OCCUtils/Topology.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | from __future__ import print_function
21 |
22 | __all__ = ["Topo", "WireExplorer", "dumpTopology"]
23 |
24 | from OCC.Core.BRep import BRep_Tool
25 |
26 | from OCC.Core.BRepTools import BRepTools_WireExplorer
27 | from OCC.Core.TopAbs import (
28 | TopAbs_VERTEX,
29 | TopAbs_EDGE,
30 | TopAbs_FACE,
31 | TopAbs_WIRE,
32 | TopAbs_SHELL,
33 | TopAbs_SOLID,
34 | TopAbs_COMPOUND,
35 | TopAbs_COMPSOLID,
36 | )
37 | from OCC.Core.TopExp import TopExp_Explorer, topexp_MapShapesAndAncestors
38 | from OCC.Core.TopTools import (
39 | TopTools_ListOfShape,
40 | TopTools_ListIteratorOfListOfShape,
41 | TopTools_IndexedDataMapOfShapeListOfShape,
42 | )
43 | from OCC.Core.TopoDS import (
44 | topods,
45 | TopoDS_Wire,
46 | TopoDS_Vertex,
47 | TopoDS_Edge,
48 | TopoDS_Face,
49 | TopoDS_Shell,
50 | TopoDS_Solid,
51 | TopoDS_Compound,
52 | TopoDS_CompSolid,
53 | topods_Edge,
54 | topods_Vertex,
55 | TopoDS_Iterator,
56 | )
57 |
58 |
59 | class WireExplorer(object):
60 | """
61 | Wire traversal
62 | """
63 |
64 | def __init__(self, wire):
65 | assert isinstance(wire, TopoDS_Wire), "not a TopoDS_Wire"
66 | self.wire = wire
67 | self.wire_explorer = BRepTools_WireExplorer(self.wire)
68 | self.done = False
69 |
70 | def _reinitialize(self):
71 | self.wire_explorer = BRepTools_WireExplorer(self.wire)
72 | self.done = False
73 |
74 | def _loop_topo(self, edges=True):
75 | if self.done:
76 | self._reinitialize()
77 | topologyType = topods_Edge if edges else topods_Vertex
78 | seq = []
79 | hashes = [] # list that stores hashes to avoid redundancy
80 | occ_seq = TopTools_ListOfShape()
81 | while self.wire_explorer.More():
82 | # loop edges
83 | if edges:
84 | current_item = self.wire_explorer.Current()
85 | # loop vertices
86 | else:
87 | current_item = self.wire_explorer.CurrentVertex()
88 | current_item_hash = current_item.__hash__()
89 | if not current_item_hash in hashes:
90 | hashes.append(current_item_hash)
91 | occ_seq.Append(current_item)
92 | self.wire_explorer.Next()
93 |
94 | # Convert occ_seq to python list
95 | occ_iterator = TopTools_ListIteratorOfListOfShape(occ_seq)
96 | while occ_iterator.More():
97 | topo_to_add = topologyType(occ_iterator.Value())
98 | seq.append(topo_to_add)
99 | occ_iterator.Next()
100 | self.done = True
101 | return iter(seq)
102 |
103 | def ordered_edges(self):
104 | return self._loop_topo(edges=True)
105 |
106 | def ordered_vertices(self):
107 | return self._loop_topo(edges=False)
108 |
109 |
110 | class Topo(object):
111 | """
112 | Topology traversal
113 | """
114 |
115 | def __init__(self, myShape, ignore_orientation=False):
116 | """
117 |
118 | implements topology traversal from any TopoDS_Shape
119 | this class lets you find how various topological entities are connected from one to another
120 | find the faces connected to an edge, find the vertices this edge is made from, get all faces connected to
121 | a vertex, and find out how many topological elements are connected from a source
122 |
123 | *note* when traversing TopoDS_Wire entities, its advised to use the specialized
124 | ``WireExplorer`` class, which will return the vertices / edges in the expected order
125 |
126 | :param myShape: the shape which topology will be traversed
127 |
128 | :param ignore_orientation: filter out TopoDS_* entities of similar TShape but different Orientation
129 |
130 | for instance, a cube has 24 edges, 4 edges for each of 6 faces
131 |
132 | that results in 48 vertices, while there are only 8 vertices that have a unique
133 | geometric coordinate
134 |
135 | in certain cases ( computing a graph from the topology ) its preferable to return
136 | topological entities that share similar geometry, though differ in orientation
137 | by setting the ``ignore_orientation`` variable
138 | to True, in case of a cube, just 12 edges and only 8 vertices will be returned
139 |
140 | for further reference see TopoDS_Shape IsEqual / IsSame methods
141 |
142 | """
143 | self.myShape = myShape
144 | self.ignore_orientation = ignore_orientation
145 |
146 | # the topoFactory dicts maps topology types and functions that can
147 | # create this topology
148 | self.topoFactory = {
149 | TopAbs_VERTEX: topods.Vertex,
150 | TopAbs_EDGE: topods.Edge,
151 | TopAbs_FACE: topods.Face,
152 | TopAbs_WIRE: topods.Wire,
153 | TopAbs_SHELL: topods.Shell,
154 | TopAbs_SOLID: topods.Solid,
155 | TopAbs_COMPOUND: topods.Compound,
156 | TopAbs_COMPSOLID: topods.CompSolid,
157 | }
158 |
159 | def _loop_topo(
160 | self, topologyType, topologicalEntity=None, topologyTypeToAvoid=None
161 | ):
162 | """
163 | this could be a faces generator for a python TopoShape class
164 | that way you can just do:
165 | for face in srf.faces:
166 | processFace(face)
167 | """
168 | topoTypes = {
169 | TopAbs_VERTEX: TopoDS_Vertex,
170 | TopAbs_EDGE: TopoDS_Edge,
171 | TopAbs_FACE: TopoDS_Face,
172 | TopAbs_WIRE: TopoDS_Wire,
173 | TopAbs_SHELL: TopoDS_Shell,
174 | TopAbs_SOLID: TopoDS_Solid,
175 | TopAbs_COMPOUND: TopoDS_Compound,
176 | TopAbs_COMPSOLID: TopoDS_CompSolid,
177 | }
178 |
179 | assert topologyType in topoTypes.keys(), "%s not one of %s" % (
180 | topologyType,
181 | topoTypes.keys(),
182 | )
183 | self.topExp = TopExp_Explorer()
184 | # use self.myShape if nothing is specified
185 | if topologicalEntity is None and topologyTypeToAvoid is None:
186 | self.topExp.Init(self.myShape, topologyType)
187 | elif topologicalEntity is None and topologyTypeToAvoid is not None:
188 | self.topExp.Init(self.myShape, topologyType, topologyTypeToAvoid)
189 | elif topologyTypeToAvoid is None:
190 | self.topExp.Init(topologicalEntity, topologyType)
191 | elif topologyTypeToAvoid:
192 | self.topExp.Init(topologicalEntity, topologyType, topologyTypeToAvoid)
193 | seq = []
194 | hashes = [] # list that stores hashes to avoid redundancy
195 | occ_seq = TopTools_ListOfShape()
196 | while self.topExp.More():
197 | current_item = self.topExp.Current()
198 | current_item_hash = current_item.__hash__()
199 |
200 | if not current_item_hash in hashes:
201 | hashes.append(current_item_hash)
202 | occ_seq.Append(current_item)
203 |
204 | self.topExp.Next()
205 | # Convert occ_seq to python list
206 | occ_iterator = TopTools_ListIteratorOfListOfShape(occ_seq)
207 | while occ_iterator.More():
208 | topo_to_add = self.topoFactory[topologyType](occ_iterator.Value())
209 | seq.append(topo_to_add)
210 | occ_iterator.Next()
211 |
212 | if self.ignore_orientation:
213 | # filter out those entities that share the same TShape
214 | # but do *not* share the same orientation
215 | filter_orientation_seq = []
216 | for i in seq:
217 | _present = False
218 | for j in filter_orientation_seq:
219 | if i.IsSame(j):
220 | _present = True
221 | break
222 | if _present is False:
223 | filter_orientation_seq.append(i)
224 | return filter_orientation_seq
225 | else:
226 | return iter(seq)
227 |
228 | def faces(self):
229 | """
230 | loops over all faces
231 | """
232 | return self._loop_topo(TopAbs_FACE)
233 |
234 | def _number_of_topo(self, iterable):
235 | n = 0
236 | for i in iterable:
237 | n += 1
238 | return n
239 |
240 | def number_of_faces(self):
241 | return self._number_of_topo(self.faces())
242 |
243 | def vertices(self):
244 | """
245 | loops over all vertices
246 | """
247 | return self._loop_topo(TopAbs_VERTEX)
248 |
249 | def number_of_vertices(self):
250 | return self._number_of_topo(self.vertices())
251 |
252 | def edges(self):
253 | """
254 | loops over all edges
255 | """
256 | return self._loop_topo(TopAbs_EDGE)
257 |
258 | def number_of_edges(self):
259 | return self._number_of_topo(self.edges())
260 |
261 | def wires(self):
262 | """
263 | loops over all wires
264 | """
265 | return self._loop_topo(TopAbs_WIRE)
266 |
267 | def number_of_wires(self):
268 | return self._number_of_topo(self.wires())
269 |
270 | def shells(self):
271 | """
272 | loops over all shells
273 | """
274 | return self._loop_topo(TopAbs_SHELL, None)
275 |
276 | def number_of_shells(self):
277 | return self._number_of_topo(self.shells())
278 |
279 | def solids(self):
280 | """
281 | loops over all solids
282 | """
283 | return self._loop_topo(TopAbs_SOLID, None)
284 |
285 | def number_of_solids(self):
286 | return self._number_of_topo(self.solids())
287 |
288 | def comp_solids(self):
289 | """
290 | loops over all compound solids
291 | """
292 | return self._loop_topo(TopAbs_COMPSOLID)
293 |
294 | def number_of_comp_solids(self):
295 | return self._number_of_topo(self.comp_solids())
296 |
297 | def compounds(self):
298 | """
299 | loops over all compounds
300 | """
301 | return self._loop_topo(TopAbs_COMPOUND)
302 |
303 | def number_of_compounds(self):
304 | return self._number_of_topo(self.compounds())
305 |
306 | def ordered_vertices_from_wire(self, wire):
307 | """
308 | @param wire: TopoDS_Wire
309 | """
310 | we = WireExplorer(wire)
311 | return we.ordered_vertices()
312 |
313 | def number_of_ordered_vertices_from_wire(self, wire):
314 | return self._number_of_topo(self.ordered_vertices_from_wire(wire))
315 |
316 | def ordered_edges_from_wire(self, wire):
317 | """
318 | @param wire: TopoDS_Wire
319 | """
320 | we = WireExplorer(wire)
321 | return we.ordered_edges()
322 |
323 | def number_of_ordered_edges_from_wire(self, wire):
324 | return self._number_of_topo(self.ordered_edges_from_wire(wire))
325 |
326 | def _map_shapes_and_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
327 | """
328 | using the same method
329 | @param topoTypeA:
330 | @param topoTypeB:
331 | @param topologicalEntity:
332 | """
333 | topo_set = set()
334 | _map = TopTools_IndexedDataMapOfShapeListOfShape()
335 | topexp_MapShapesAndAncestors(self.myShape, topoTypeA, topoTypeB, _map)
336 | results = _map.FindFromKey(topologicalEntity)
337 | if results.Size() == 0:
338 | yield None
339 |
340 | topology_iterator = TopTools_ListIteratorOfListOfShape(results)
341 | while topology_iterator.More():
342 |
343 | topo_entity = self.topoFactory[topoTypeB](topology_iterator.Value())
344 |
345 | # return the entity if not in set
346 | # to assure we're not returning entities several times
347 | if not topo_entity in topo_set:
348 | if self.ignore_orientation:
349 | unique = True
350 | for i in topo_set:
351 | if i.IsSame(topo_entity):
352 | unique = False
353 | break
354 | if unique:
355 | yield topo_entity
356 | else:
357 | yield topo_entity
358 |
359 | topo_set.add(topo_entity)
360 | topology_iterator.Next()
361 |
362 | def _number_shapes_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
363 | """returns the number of shape ancestors
364 | If you want to know how many edges a faces has:
365 | _number_shapes_ancestors(self, TopAbs_EDGE, TopAbs_FACE, edg)
366 | will return the number of edges a faces has
367 | @param topoTypeA:
368 | @param topoTypeB:
369 | @param topologicalEntity:
370 | """
371 | topo_set = set()
372 | _map = TopTools_IndexedDataMapOfShapeListOfShape()
373 | topexp_MapShapesAndAncestors(self.myShape, topoTypeA, topoTypeB, _map)
374 | results = _map.FindFromKey(topologicalEntity)
375 | if results.Size() == 0:
376 | return None
377 | topology_iterator = TopTools_ListIteratorOfListOfShape(results)
378 | while topology_iterator.More():
379 | topo_set.add(topology_iterator.Value())
380 | topology_iterator.Next()
381 | return len(topo_set)
382 |
383 | # ======================================================================
384 | # EDGE <-> FACE
385 | # ======================================================================
386 | def faces_from_edge(self, edge):
387 | """
388 |
389 | :param edge:
390 | :return:
391 | """
392 | return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)
393 |
394 | def number_of_faces_from_edge(self, edge):
395 | """
396 |
397 | :param edge:
398 | :return:
399 | """
400 | return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)
401 |
402 | def edges_from_face(self, face):
403 | """
404 |
405 | :param face:
406 | :return:
407 | """
408 | return self._loop_topo(TopAbs_EDGE, face)
409 |
410 | def number_of_edges_from_face(self, face):
411 | cnt = 0
412 | for i in self._loop_topo(TopAbs_EDGE, face):
413 | cnt += 1
414 | return cnt
415 |
416 | # ======================================================================
417 | # VERTEX <-> EDGE
418 | # ======================================================================
419 | def vertices_from_edge(self, edg):
420 | return self._loop_topo(TopAbs_VERTEX, edg)
421 |
422 | def number_of_vertices_from_edge(self, edg):
423 | cnt = 0
424 | for i in self._loop_topo(TopAbs_VERTEX, edg):
425 | cnt += 1
426 | return cnt
427 |
428 | def edges_from_vertex(self, vertex):
429 | return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_EDGE, vertex)
430 |
431 | def number_of_edges_from_vertex(self, vertex):
432 | return self._number_shapes_ancestors(TopAbs_VERTEX, TopAbs_EDGE, vertex)
433 |
434 | # ======================================================================
435 | # WIRE <-> EDGE
436 | # ======================================================================
437 | def edges_from_wire(self, wire):
438 | return self._loop_topo(TopAbs_EDGE, wire)
439 |
440 | def number_of_edges_from_wire(self, wire):
441 | cnt = 0
442 | for i in self._loop_topo(TopAbs_EDGE, wire):
443 | cnt += 1
444 | return cnt
445 |
446 | def wires_from_edge(self, edg):
447 | return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)
448 |
449 | def wires_from_vertex(self, edg):
450 | return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_WIRE, edg)
451 |
452 | def number_of_wires_from_edge(self, edg):
453 | return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)
454 |
455 | # ======================================================================
456 | # WIRE <-> FACE
457 | # ======================================================================
458 | def wires_from_face(self, face):
459 | return self._loop_topo(TopAbs_WIRE, face)
460 |
461 | def number_of_wires_from_face(self, face):
462 | cnt = 0
463 | for i in self._loop_topo(TopAbs_WIRE, face):
464 | cnt += 1
465 | return cnt
466 |
467 | def faces_from_wire(self, wire):
468 | return self._map_shapes_and_ancestors(TopAbs_WIRE, TopAbs_FACE, wire)
469 |
470 | def number_of_faces_from_wires(self, wire):
471 | return self._number_shapes_ancestors(TopAbs_WIRE, TopAbs_FACE, wire)
472 |
473 | # ======================================================================
474 | # VERTEX <-> FACE
475 | # ======================================================================
476 | def faces_from_vertex(self, vertex):
477 | return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_FACE, vertex)
478 |
479 | def number_of_faces_from_vertex(self, vertex):
480 | return self._number_shapes_ancestors(TopAbs_VERTEX, TopAbs_FACE, vertex)
481 |
482 | def vertices_from_face(self, face):
483 | return self._loop_topo(TopAbs_VERTEX, face)
484 |
485 | def number_of_vertices_from_face(self, face):
486 | cnt = 0
487 | for i in self._loop_topo(TopAbs_VERTEX, face):
488 | cnt += 1
489 | return cnt
490 |
491 | # ======================================================================
492 | # FACE <-> SOLID
493 | # ======================================================================
494 | def solids_from_face(self, face):
495 | return self._map_shapes_and_ancestors(TopAbs_FACE, TopAbs_SOLID, face)
496 |
497 | def number_of_solids_from_face(self, face):
498 | return self._number_shapes_ancestors(TopAbs_FACE, TopAbs_SOLID, face)
499 |
500 | def faces_from_solids(self, solid):
501 | return self._loop_topo(TopAbs_FACE, solid)
502 |
503 | def number_of_faces_from_solids(self, solid):
504 | cnt = 0
505 | for i in self._loop_topo(TopAbs_FACE, solid):
506 | cnt += 1
507 | return cnt
508 |
509 |
510 | def dumpTopology(shape, level=0):
511 | """
512 | Print the details of an object from the top down
513 | """
514 | brt = BRep_Tool()
515 | s = shape.ShapeType()
516 | if s == TopAbs_VERTEX:
517 | pnt = brt.Pnt(topods_Vertex(shape))
518 | print(
519 | ".." * level
520 | + "" % (hash(shape), pnt.X(), pnt.Y(), pnt.Z())
521 | )
522 | else:
523 | print(".." * level, end="")
524 | print(shapeTypeString(shape))
525 | it = TopoDS_Iterator(shape)
526 | while it.More():
527 | shp = it.Value()
528 | it.Next()
529 | dumpTopology(shp, level + 1)
530 |
531 |
532 | def shapeTypeString(shape):
533 | st = shape.ShapeType()
534 | s = "?"
535 | if st == TopAbs_VERTEX:
536 | s = "Vertex"
537 | if st == TopAbs_SOLID:
538 | s = "Solid"
539 | if st == TopAbs_EDGE:
540 | s = "Edge"
541 | if st == TopAbs_FACE:
542 | s = "Face"
543 | if st == TopAbs_SHELL:
544 | s = "Shell"
545 | if st == TopAbs_WIRE:
546 | s = "Wire"
547 | if st == TopAbs_COMPOUND:
548 | s = "Compound."
549 | if st == TopAbs_COMPSOLID:
550 | s = "Compsolid."
551 | return "%s: %i" % (s, hash(shape))
552 |
--------------------------------------------------------------------------------
/OCCUtils/__init__.py:
--------------------------------------------------------------------------------
1 | from OCCUtils.Common import get_boundingbox
2 | from OCCUtils.Topology import Topo
3 |
--------------------------------------------------------------------------------
/OCCUtils/base.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2013 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | """
19 | Please note the following;
20 | @readonly
21 | means that the decorated method is a readonly descriptor
22 | @property
23 | means that the decorated method can be set / get using the descriptor
24 | ( irony is that just using @property *would*
25 | result in a readonly descriptor :")
26 |
27 | Sometimes a set of methods should be contained in another module or class,
28 | or simply grouped.
29 | For instance the set of methods after:
30 | #===========================================================================
31 | # Curve.local_properties
32 | #===========================================================================
33 |
34 | Can be a module, class or namespace.
35 |
36 | """
37 |
38 | import functools
39 |
40 | from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Copy
41 | from OCC.Core.BRepGProp import (
42 | brepgprop_VolumeProperties,
43 | brepgprop_LinearProperties,
44 | brepgprop_SurfaceProperties,
45 | )
46 | from OCC.Core.BRepCheck import (
47 | BRepCheck_Vertex,
48 | BRepCheck_Edge,
49 | BRepCheck_Wire,
50 | BRepCheck_Face,
51 | BRepCheck_Shell,
52 | BRepCheck_Analyzer,
53 | )
54 | from OCC.Core.GProp import GProp_GProps
55 | from OCC.Display.SimpleGui import init_display
56 |
57 | from OCCUtils.Common import get_boundingbox
58 | from OCCUtils.Construct import make_vertex, TOLERANCE
59 | from OCCUtils.types_lut import shape_lut, topo_lut, curve_lut, surface_lut
60 |
61 | # ===========================================================================
62 | # DISPLAY
63 | # ===========================================================================
64 | global display
65 |
66 |
67 | class singleton(object):
68 | def __init__(self, cls):
69 | self.cls = cls
70 | self.instance_container = []
71 |
72 | def __call__(self, *args, **kwargs):
73 | if not len(self.instance_container):
74 | cls = functools.partial(self.cls, *args, **kwargs)
75 | self.instance_container.append(cls())
76 | return self.instance_container[0]
77 |
78 |
79 | @singleton
80 | class Display(object):
81 | def __init__(self):
82 | (
83 | self.display,
84 | self.start_display,
85 | self.add_menu,
86 | self.add_function_to_menu,
87 | ) = init_display()
88 |
89 | def __call__(self, *args, **kwargs):
90 | return self.display.DisplayShape(*args, **kwargs)
91 |
92 |
93 | # ============
94 | # base class
95 | # ============
96 |
97 |
98 | class BaseObject(object):
99 | """base class for all objects"""
100 |
101 | def __init__(self, name=None, tolerance=TOLERANCE):
102 | self.GlobalProperties = GlobalProperties(self)
103 | self.name = name
104 | self._dirty = False
105 | self.tolerance = tolerance
106 | self.display_set = False
107 |
108 | @property
109 | def is_dirty(self):
110 | """when an object is dirty, its topology will be
111 | rebuild when update is called"""
112 | return self._dirty
113 |
114 | @is_dirty.setter
115 | def is_dirty(self, _bool):
116 | self._dirty = bool(_bool)
117 |
118 | @property
119 | def topo_type(self):
120 | return topo_lut[self.ShapeType()]
121 |
122 | @property
123 | def geom_type(self):
124 | if self.topo_type == "edge":
125 | return curve_lut[self.ShapeType()]
126 | if self.topo_type == "face":
127 | return surface_lut[self.adaptor.GetType()]
128 | else:
129 | raise ValueError("geom_type works only for edges and faces...")
130 |
131 | def set_display(self, display):
132 | if hasattr(display, "DisplayShape"):
133 | self.display_set = True
134 | self.display = display
135 | else:
136 | raise ValueError("not a display")
137 |
138 | def check(self):
139 | """ """
140 | _check = dict(
141 | vertex=BRepCheck_Vertex,
142 | edge=BRepCheck_Edge,
143 | wire=BRepCheck_Wire,
144 | face=BRepCheck_Face,
145 | shell=BRepCheck_Shell,
146 | )
147 | _check[self.topo_type]
148 | # TODO: BRepCheck will be able to inform *what* actually is the matter,
149 | # though implementing this still is a bit of work...
150 | raise NotImplementedError
151 |
152 | def is_valid(self):
153 | analyse = BRepCheck_Analyzer(self)
154 | ok = analyse.IsValid()
155 | if ok:
156 | return True
157 | else:
158 | return False
159 |
160 | def copy(self):
161 | """
162 |
163 | :return:
164 | """
165 | cp = BRepBuilderAPI_Copy(self)
166 | cp.Perform(self)
167 | # get the class, construct a new instance
168 | # cast the cp.Shape() to its specific TopoDS topology
169 | _copy = self.__class__(shape_lut(cp.Shape()))
170 | return _copy
171 |
172 | def distance(self, other):
173 | """
174 | return the minimum distance
175 |
176 | :return: minimum distance,
177 | minimum distance points on shp1
178 | minimum distance points on shp2
179 | """
180 | return minimum_distance(self, other)
181 |
182 | def show(self, *args, **kwargs):
183 | """
184 | renders the topological entity in the viewer
185 |
186 | :param update: redraw the scene or not
187 | """
188 | if not self.display_set:
189 | Display()(self, *args, **kwargs)
190 | else:
191 | self.disp.DisplayShape(*args, **kwargs)
192 |
193 | def build(self):
194 | if self.name.startswith("Vertex"):
195 | self = make_vertex(self)
196 |
197 | def __eq__(self, other):
198 | return self.IsEqual(other)
199 |
200 | def __ne__(self, other):
201 | return not self.__eq__(other)
202 |
203 |
204 | class GlobalProperties(object):
205 | """
206 | global properties for all topologies
207 | """
208 |
209 | def __init__(self, instance):
210 | self.instance = instance
211 |
212 | @property
213 | def system(self):
214 | self._system = GProp_GProps()
215 | # todo, type should be abstracted with TopoDS...
216 | _topo_type = self.instance.topo_type
217 | if _topo_type == "face" or _topo_type == "shell":
218 | brepgprop_SurfaceProperties(self.instance, self._system)
219 | elif _topo_type == "edge":
220 | brepgprop_LinearProperties(self.instance, self._system)
221 | elif _topo_type == "solid":
222 | brepgprop_VolumeProperties(self.instance, self._system)
223 | return self._system
224 |
225 | def centre(self):
226 | """
227 | :return: centre of the entity
228 | """
229 | return self.system.CentreOfMass()
230 |
231 | def inertia(self):
232 | """returns the inertia matrix"""
233 | return self.system.MatrixOfInertia(), self.system.MomentOfInertia()
234 |
235 | def area(self):
236 | """returns the area of the surface"""
237 | return self.system.Mass()
238 |
239 | def bbox(self):
240 | """
241 | returns the bounding box of the face
242 | """
243 | return get_boundingbox(self.instance)
244 |
--------------------------------------------------------------------------------
/OCCUtils/edge.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Curve
19 | from OCC.Core.GCPnts import GCPnts_UniformAbscissa
20 | from OCC.Core.Geom import Geom_OffsetCurve, Geom_TrimmedCurve
21 | from OCC.Core.TopExp import topexp
22 | from OCC.Core.TopoDS import TopoDS_Edge, TopoDS_Vertex, TopoDS_Face
23 | from OCC.Core.gp import gp_Vec, gp_Dir, gp_Pnt
24 | from OCC.Core.GeomLProp import GeomLProp_CurveTool
25 | from OCC.Core.BRepLProp import BRepLProp_CLProps
26 | from OCC.Core.GeomLib import geomlib
27 | from OCC.Core.GCPnts import GCPnts_AbscissaPoint
28 | from OCC.Core.GeomAPI import GeomAPI_ProjectPointOnCurve
29 | from OCC.Core.ShapeAnalysis import ShapeAnalysis_Edge
30 | from OCC.Core.BRep import BRep_Tool, BRep_Tool_Continuity
31 | from OCC.Core.BRepIntCurveSurface import BRepIntCurveSurface_Inter
32 |
33 | # high-level
34 | from OCCUtils.Common import vertex2pnt, minimum_distance, assert_isdone, fix_continuity
35 | from OCCUtils.Construct import make_edge
36 | from OCCUtils.types_lut import geom_lut
37 | from OCCUtils.base import BaseObject
38 |
39 |
40 | class IntersectCurve(object):
41 | def __init__(self, instance):
42 | self.instance = instance
43 |
44 | def intersect(self, other, tolerance=1e-2):
45 | """Intersect self with a point, curve, edge, face, solid
46 | method wraps dealing with the various topologies
47 | """
48 | if isinstance(other, TopoDS_Face):
49 | face_curve_intersect = BRepIntCurveSurface_Inter()
50 | face_curve_intersect.Init(other, self.instance.adaptor.Curve(), tolerance)
51 | pnts = []
52 | while face_curve_intersect.More():
53 | next(face_curve_intersect)
54 | pnts.append(face_curve_intersect.Pnt())
55 | return pnts
56 |
57 |
58 | class DiffGeomCurve(object):
59 | def __init__(self, instance):
60 | self.instance = instance
61 | self._local_props = BRepLProp_CLProps(
62 | self.instance.adaptor, 2, self.instance.tolerance
63 | )
64 |
65 | @property
66 | def _curvature(self):
67 | return self._local_props
68 |
69 | def radius(self, u):
70 | """returns the radius at u"""
71 | # NOT SO SURE IF THIS IS THE SAME THING!!!
72 | self._curvature.SetParameter(u)
73 | pnt = gp_Pnt()
74 | self._curvature.CentreOfCurvature(pnt)
75 | return pnt
76 |
77 | def curvature(self, u):
78 | # ugly
79 | self._curvature.SetParameter(u)
80 | return self._curvature.Curvature()
81 |
82 | def tangent(self, u):
83 | """sets or gets ( iff vector ) the tangency at the u parameter
84 | tangency can be constrained so when setting the tangency,
85 | you're constrainting it in fact
86 | """
87 | self._curvature.SetParameter(u)
88 | if self._curvature.IsTangentDefined():
89 | ddd = gp_Dir()
90 | self._curvature.Tangent(ddd)
91 | return ddd
92 | else:
93 | raise ValueError("no tangent defined")
94 |
95 | def normal(self, u):
96 | """returns the normal at u
97 |
98 | computes the main normal if no normal is found
99 | see:
100 | www.opencascade.org/org/forum/thread_645+&cd=10&hl=nl&ct=clnk&gl=nl
101 | """
102 | try:
103 | self._curvature.SetParameter(u)
104 | a_dir = gp_Dir()
105 | self._curvature.Normal(a_dir)
106 | return a_dir
107 | except:
108 | raise ValueError("no normal was found")
109 |
110 | def derivative(self, u, n):
111 | """
112 | returns n derivatives at parameter b
113 | """
114 | self._curvature.SetParameter(u)
115 | deriv = {
116 | 1: self._curvature.D1,
117 | 2: self._curvature.D2,
118 | 3: self._curvature.D3,
119 | }
120 | try:
121 | return deriv[n]
122 | except KeyError:
123 | raise AssertionError("n of derivative is one of [1,2,3]")
124 |
125 | def points_from_tangential_deflection(self):
126 | pass
127 |
128 |
129 | # ===========================================================================
130 | # Curve.Construct
131 | # ===========================================================================
132 |
133 |
134 | class ConstructFromCurve:
135 | def __init__(self, instance):
136 | self.instance = instance
137 |
138 | def make_offset(self, offset, vec):
139 | """
140 | returns an offsetted curve
141 | @param offset: the distance between self.crv and the curve to offset
142 | @param vec: offset direction
143 | """
144 | return Geom_OffsetCurve(self.instance.h_crv, offset, vec)
145 |
146 |
147 | class Edge(TopoDS_Edge, BaseObject):
148 | def __init__(self, edge):
149 | assert isinstance(edge, TopoDS_Edge), (
150 | "need a TopoDS_Edge, got a %s" % edge.__class__
151 | )
152 | assert not edge.IsNull()
153 | super(Edge, self).__init__()
154 | BaseObject.__init__(self, "edge")
155 | # we need to copy the base shape using the following three
156 | # lines
157 | assert self.IsNull()
158 | self.TShape(edge.TShape())
159 | self.Location(edge.Location())
160 | self.Orientation(edge.Orientation())
161 | assert not self.IsNull()
162 |
163 | # tracking state
164 | self._local_properties_init = False
165 | self._curvature_init = False
166 | self._geometry_lookup_init = False
167 | self._curve = None
168 | self._adaptor = None
169 |
170 | # instantiating cooperative classes
171 | # cooperative classes are distinct through CamelCaps from
172 | # normal method -> pep8
173 | self.DiffGeom = DiffGeomCurve(self)
174 | self.Intersect = IntersectCurve(self)
175 | self.Construct = ConstructFromCurve(self)
176 |
177 | # GeomLProp object
178 | self._curvature = None
179 |
180 | def is_closed(self):
181 | return self.adaptor.IsClosed()
182 |
183 | def is_periodic(self):
184 | return self.adaptor.IsPeriodic()
185 |
186 | def is_rational(self):
187 | return self.adaptor.IsRational()
188 |
189 | def continuity(self):
190 | return self.adaptor.Continuity
191 |
192 | def degree(self):
193 | if "line" in self.type:
194 | return 1
195 | elif "curve" in self.type:
196 | return self.adaptor.Degree()
197 | else:
198 | # hyperbola, parabola, circle
199 | return 2
200 |
201 | def nb_knots(self):
202 | return self.adaptor.NbKnots()
203 |
204 | def nb_poles(self):
205 | return self.adaptor.NbPoles()
206 |
207 | @property
208 | def curve(self):
209 | if self._curve is not None and not self.is_dirty:
210 | pass
211 | else:
212 | self._curve = BRep_Tool().Curve(self)[0]
213 | return self._curve
214 |
215 | @property
216 | def adaptor(self):
217 | if self._adaptor is not None and not self.is_dirty:
218 | pass
219 | else:
220 | self._adaptor = BRepAdaptor_Curve(self)
221 | return self._adaptor
222 |
223 | @property
224 | def type(self):
225 | return geom_lut[self.adaptor.Curve().GetType()]
226 |
227 | def pcurve(self, face):
228 | """
229 | computes the 2d parametric spline that lies on the surface of the face
230 | :return: Geom2d_Curve, u, v
231 | """
232 | crv, u, v = BRep_Tool().CurveOnSurface(self, face)
233 | return crv, u, v
234 |
235 | def _local_properties(self):
236 | self._lprops_curve_tool = GeomLProp_CurveTool()
237 | self._local_properties_init = True
238 |
239 | def domain(self):
240 | """returns the u,v domain of the curve"""
241 | return self.adaptor.FirstParameter(), self.adaptor.LastParameter()
242 |
243 | # ===========================================================================
244 | # Curve.GlobalProperties
245 | # ===========================================================================
246 |
247 | def length(self, lbound=None, ubound=None, tolerance=1e-5):
248 | """returns the curve length
249 | if either lbound | ubound | both are given, than the length
250 | of the curve will be measured over that interval
251 | """
252 | _min, _max = self.domain()
253 | if _min < self.adaptor.FirstParameter():
254 | raise ValueError(
255 | "the lbound argument is lower than the first parameter of the curve: %s "
256 | % (self.adaptor.FirstParameter())
257 | )
258 | if _max > self.adaptor.LastParameter():
259 | raise ValueError(
260 | "the ubound argument is greater than the last parameter of the curve: %s "
261 | % (self.adaptor.LastParameter())
262 | )
263 |
264 | lbound = _min if lbound is None else lbound
265 | ubound = _max if ubound is None else ubound
266 | return GCPnts_AbscissaPoint().Length(self.adaptor, lbound, ubound, tolerance)
267 |
268 | # ===========================================================================
269 | # Curve.modify
270 | # ===========================================================================
271 |
272 | def trim(self, lbound, ubound):
273 | """
274 | trim the curve
275 | @param lbound:
276 | @param ubound:
277 | """
278 | a, b = sorted([lbound, ubound])
279 | tr = Geom_TrimmedCurve(self.adaptor.Curve().Curve(), a, b)
280 | return Edge(make_edge(tr))
281 |
282 | def extend_by_point(self, pnt, degree=3, beginning=True):
283 | """extends the curve to point
284 |
285 | does not extend if the degree of self.curve > 3
286 | @param pnt:
287 | @param degree:
288 | @param beginning:
289 | """
290 | if self.degree > 3:
291 | raise ValueError(
292 | "to extend you self.curve should be <= 3, is %s" % (self.degree)
293 | )
294 | return geomlib.ExtendCurveToPoint(self.curve, pnt, degree, beginning)
295 |
296 | # ===========================================================================
297 | # Curve.
298 | # ===========================================================================
299 | def closest(self, other):
300 | return minimum_distance(self, other)
301 |
302 | def project_vertex(self, pnt_or_vertex):
303 | """returns the closest orthogonal project on `pnt` on edge"""
304 | if isinstance(pnt_or_vertex, TopoDS_Vertex):
305 | pnt_or_vertex = vertex2pnt(pnt_or_vertex)
306 |
307 | poc = GeomAPI_ProjectPointOnCurve(pnt_or_vertex, self.curve)
308 | return poc.LowerDistanceParameter(), poc.NearestPoint()
309 |
310 | def distance_on_curve(self, distance, close_parameter, estimate_parameter):
311 | """returns the parameter if there is a parameter
312 | on the curve with a distance length from u
313 | raises OutOfBoundary if no such parameter exists
314 | """
315 | gcpa = GCPnts_AbscissaPoint(
316 | self.adaptor, distance, close_parameter, estimate_parameter, 1e-5
317 | )
318 | with assert_isdone(gcpa, "couldnt compute distance on curve"):
319 | return gcpa.Parameter()
320 |
321 | def mid_point(self):
322 | """
323 | :return: the parameter at the mid point of the curve, and
324 | its corresponding gp_Pnt
325 | """
326 | _min, _max = self.domain()
327 | _mid = (_min + _max) / 2.0
328 | return _mid, self.adaptor.Value(_mid)
329 |
330 | def divide_by_number_of_points(self, n_pts, lbound=None, ubound=None):
331 | """returns a nested list of parameters and points on the edge
332 | at the requested interval [(param, gp_Pnt),...]
333 | """
334 | _lbound, _ubound = self.domain()
335 | if lbound:
336 | _lbound = lbound
337 | elif ubound:
338 | _ubound = ubound
339 |
340 | # minimally two points or a Standard_ConstructionError is raised
341 | if n_pts <= 1:
342 | n_pts = 2
343 |
344 | try:
345 | npts = GCPnts_UniformAbscissa(self.adaptor, n_pts, _lbound, _ubound)
346 | except:
347 | print("Warning : GCPnts_UniformAbscissa failed")
348 | if npts.IsDone():
349 | tmp = []
350 | for i in xrange(1, npts.NbPoints() + 1):
351 | param = npts.Parameter(i)
352 | pnt = self.adaptor.Value(param)
353 | tmp.append((param, pnt))
354 | return tmp
355 | else:
356 | return None
357 |
358 | def __eq__(self, other):
359 | if hasattr(other, "topo"):
360 | return self.IsEqual(other)
361 | else:
362 | return self.IsEqual(other)
363 |
364 | def __ne__(self, other):
365 | return not self.__eq__(other)
366 |
367 | def first_vertex(self):
368 | return topexp.FirstVertex(self)
369 |
370 | def last_vertex(self):
371 | return topexp.LastVertex(self)
372 |
373 | def common_vertex(self, edge):
374 | vert = TopoDS_Vertex()
375 | if topexp.CommonVertex(self, edge, vert):
376 | return vert
377 | else:
378 | return False
379 |
380 | def as_vec(self):
381 | if self.is_line():
382 | first, last = map(vertex2pnt, [self.first_vertex(), self.last_vertex()])
383 | return gp_Vec(first, last)
384 | else:
385 | raise ValueError(
386 | "edge is not a line, hence no meaningful vector can be returned"
387 | )
388 |
389 | # ===========================================================================
390 | # Curve.
391 | # ===========================================================================
392 |
393 | def parameter_to_point(self, u):
394 | """returns the coordinate at parameter u"""
395 | return self.adaptor.Value(u)
396 |
397 | def fix_continuity(self, continuity):
398 | """
399 | splits an edge to achieve a level of continuity
400 | :param continuity: GeomAbs_C*
401 | """
402 | return fix_continuity(self, continuity)
403 |
404 | def continuity_from_faces(self, f1, f2):
405 | return BRep_Tool_Continuity(self, f1, f2)
406 |
407 | # ===========================================================================
408 | # Curve.
409 | # ===========================================================================
410 |
411 | def is_line(self):
412 | """checks if the curve is planar"""
413 | if self.nb_knots() == 2 and self.nb_poles() == 2:
414 | return True
415 | else:
416 | return False
417 |
418 | def is_seam(self, face):
419 | """
420 | :return: True if the edge has two pcurves on one surface
421 | ( in the case of a sphere for example... )
422 | """
423 | sae = ShapeAnalysis_Edge()
424 | return sae.IsSeam(self, face)
425 |
426 | def is_edge_on_face(self, face):
427 | """checks whether curve lies on a surface or a face"""
428 | return ShapeAnalysis_Edge().HasPCurve(self, face)
429 |
430 | # ===========================================================================
431 | # Curve.graphic
432 | # ===========================================================================
433 | def show(self):
434 | """
435 | poles, knots, should render all slightly different.
436 | here's how...
437 |
438 | http://www.opencascade.org/org/forum/thread_1125/
439 | """
440 | super(Edge, self).show()
441 |
442 |
443 | if __name__ == "__main__":
444 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
445 | from OCCUtils.Topology import Topo
446 |
447 | b = BRepPrimAPI_MakeBox(10, 20, 30).Shape()
448 | t = Topo(b)
449 | ed = next(t.edges())
450 | my_e = Edge(ed)
451 | print(my_e.tolerance)
452 |
--------------------------------------------------------------------------------
/OCCUtils/face.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2013 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.BRep import BRep_Tool_Surface, BRep_Tool
19 | from OCC.Core.BRepTopAdaptor import BRepTopAdaptor_FClass2d
20 | from OCC.Core.Geom import Geom_Curve
21 | from OCC.Core.GeomAPI import GeomAPI_ProjectPointOnSurf
22 | from OCC.Core.GeomLib import GeomLib_IsPlanarSurface
23 | from OCC.Core.TopAbs import TopAbs_IN
24 | from OCC.Core.TopExp import topexp
25 | from OCC.Core.TopoDS import TopoDS_Vertex, TopoDS_Face, TopoDS_Edge
26 | from OCC.Core.GeomLProp import GeomLProp_SLProps
27 | from OCC.Core.BRepTools import breptools_UVBounds
28 | from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
29 | from OCC.Core.ShapeAnalysis import ShapeAnalysis_Surface
30 | from OCC.Core.GeomProjLib import geomprojlib
31 | from OCC.Core.Adaptor3d import Adaptor3d_IsoCurve
32 | from OCC.Core.gp import gp_Pnt2d, gp_Dir
33 |
34 | from OCCUtils.base import BaseObject
35 | from OCCUtils.edge import Edge
36 | from OCCUtils.Construct import TOLERANCE, to_adaptor_3d
37 | from OCCUtils.Topology import Topo, WireExplorer
38 |
39 |
40 | class DiffGeomSurface(object):
41 | def __init__(self, instance):
42 | self.instance = instance
43 | self._curvature = None
44 | self._curvature_initiated = False
45 |
46 | def curvature(self, u, v):
47 | """returns the curvature at the u parameter
48 | the curvature object can be returned too using
49 | curvatureType == curvatureType
50 | curvatureTypes are:
51 | gaussian
52 | minimum
53 | maximum
54 | mean
55 | curvatureType
56 | """
57 | if not self._curvature_initiated:
58 | self._curvature = GeomLProp_SLProps(self.instance.surface, u, v, 2, 1e-7)
59 |
60 | _domain = self.instance.domain()
61 | if u in _domain or v in _domain:
62 | print("<<>>")
63 | div = 1000
64 | delta_u, delta_v = (_domain[0] - _domain[1]) / div, (
65 | _domain[2] - _domain[3]
66 | ) / div
67 |
68 | if u in _domain:
69 | low, hi = u - _domain[0], u - _domain[1]
70 | if low < hi:
71 | u = u - delta_u
72 | else:
73 | u = u + delta_u
74 |
75 | if v in _domain:
76 | low, hi = v - _domain[2], v - _domain[3]
77 | if low < hi:
78 | v = v - delta_v
79 | else:
80 | v = v + delta_v
81 |
82 | self._curvature.SetParameters(u, v)
83 | self._curvature_initiated = True
84 |
85 | return self._curvature
86 |
87 | def gaussian_curvature(self, u, v):
88 | return self.curvature(u, v).GaussianCurvature()
89 |
90 | def min_curvature(self, u, v):
91 | return self.curvature(u, v).MinCurvature()
92 |
93 | def mean_curvature(self, u, v):
94 | return self.curvature(u, v).MeanCurvature()
95 |
96 | def max_curvature(self, u, v):
97 | return self.curvature(u, v).MaxCurvature()
98 |
99 | def normal(self, u, v):
100 | # TODO: should make this return a gp_Vec
101 | curv = self.curvature(u, v)
102 | if curv.IsNormalDefined():
103 | return curv.Normal()
104 | else:
105 | raise ValueError("normal is not defined at u,v: {0}, {1}".format(u, v))
106 |
107 | def tangent(self, u, v):
108 | dU, dV = gp_Dir(), gp_Dir()
109 | curv = self.curvature(u, v)
110 | if curv.IsTangentUDefined() and curv.IsTangentVDefined():
111 | curv.TangentU(dU), curv.TangentV(dV)
112 | return dU, dV
113 | else:
114 | return None, None
115 |
116 | def radius(self, u, v):
117 | """returns the radius at u"""
118 | # TODO: SHOULD WE RETURN A SIGNED RADIUS? ( get rid of abs() )?
119 | try:
120 | _crv_min = 1.0 / self.min_curvature(u, v)
121 | except ZeroDivisionError:
122 | _crv_min = 0.0
123 |
124 | try:
125 | _crv_max = 1.0 / self.max_curvature(u, v)
126 | except ZeroDivisionError:
127 | _crv_max = 0.0
128 | return abs((_crv_min + _crv_max) / 2.0)
129 |
130 |
131 | class Face(TopoDS_Face, BaseObject):
132 | """high level surface API
133 | object is a Face if part of a Solid
134 | otherwise the same methods do apply, apart from the topology obviously
135 | """
136 |
137 | def __init__(self, face):
138 | """ """
139 | assert isinstance(face, TopoDS_Face), (
140 | "need a TopoDS_Face, got a %s" % face.__class__
141 | )
142 | assert not face.IsNull()
143 | super(Face, self).__init__()
144 | BaseObject.__init__(self, "face")
145 | # we need to copy the base shape using the following three
146 | # lines
147 | assert self.IsNull()
148 | self.TShape(face.TShape())
149 | self.Location(face.Location())
150 | self.Orientation(face.Orientation())
151 | assert not self.IsNull()
152 |
153 | # cooperative classes
154 | self.DiffGeom = DiffGeomSurface(self)
155 |
156 | # STATE; whether cooperative classes are yet initialized
157 | self._curvature_initiated = False
158 | self._geometry_lookup_init = False
159 |
160 | # ===================================================================
161 | # properties
162 | # ===================================================================
163 | self._h_srf = None
164 | self._srf = None
165 | self._adaptor = None
166 | self._classify_uv = (
167 | None # cache the u,v classifier, no need to rebuild for every sample
168 | )
169 | self._topo = None
170 |
171 | # aliasing of useful methods
172 | def is_u_periodic(self):
173 | return self.adaptor.IsUPeriodic()
174 |
175 | def is_v_periodic(self):
176 | return self.adaptor.IsVPeriodic()
177 |
178 | def is_u_closed(self):
179 | return self.adaptor.IsUClosed()
180 |
181 | def is_v_closed(self):
182 | return self.adaptor.IsVClosed()
183 |
184 | def is_u_rational(self):
185 | return self.adaptor.IsURational()
186 |
187 | def is_v_rational(self):
188 | return self.adaptor.IsVRational()
189 |
190 | def u_degree(self):
191 | return self.adaptor.UDegree()
192 |
193 | def v_degree(self):
194 | return self.adaptor.VDegree()
195 |
196 | def u_continuity(self):
197 | return self.adaptor.UContinuity()
198 |
199 | def v_continuity(self):
200 | return self.adaptor.VContinuity()
201 |
202 | def domain(self):
203 | """the u,v domain of the curve
204 | :return: UMin, UMax, VMin, VMax
205 | """
206 | return breptools_UVBounds(self)
207 |
208 | def mid_point(self):
209 | """
210 | :return: the parameter at the mid point of the face,
211 | and its corresponding gp_Pnt
212 | """
213 | u_min, u_max, v_min, v_max = self.domain()
214 | u_mid = (u_min + u_max) / 2.0
215 | v_mid = (v_min + v_max) / 2.0
216 | return ((u_mid, v_mid), self.adaptor.Value(u_mid, v_mid))
217 |
218 | @property
219 | def topo(self):
220 | if self._topo is not None:
221 | return self._topo
222 | else:
223 | self._topo = Topo(self)
224 | return self._topo
225 |
226 | @property
227 | def surface(self):
228 | if self._srf is None or self.is_dirty:
229 | self._srf = BRep_Tool_Surface(self)
230 | return self._srf
231 |
232 | @property
233 | def adaptor(self):
234 | if self._adaptor is not None and not self.is_dirty:
235 | pass
236 | else:
237 | self._adaptor = BRepAdaptor_Surface(self)
238 | return self._adaptor
239 |
240 | def is_closed(self):
241 | sa = ShapeAnalysis_Surface(self.surface)
242 | return sa.IsUClosed(), sa.IsVClosed()
243 |
244 | def is_planar(self, tol=TOLERANCE):
245 | """checks if the surface is planar within a tolerance
246 | :return: bool, gp_Pln
247 | """
248 | is_planar_surface = GeomLib_IsPlanarSurface(self.surface, tol)
249 | return is_planar_surface.IsPlanar()
250 |
251 | def is_trimmed(self):
252 | """
253 | :return: True if the Wire delimiting the Face lies on the bounds
254 | of the surface
255 | if this is not the case, the wire represents a contour that delimits
256 | the face [ think cookie cutter ]
257 | and implies that the surface is trimmed
258 | """
259 | _round = lambda x: round(x, 3)
260 | a = map(_round, breptools_UVBounds(self))
261 | b = map(_round, self.adaptor.Surface().Surface().Bounds())
262 | if a != b:
263 | print("a,b", a, b)
264 | return True
265 | return False
266 |
267 | def on_trimmed(self, u, v):
268 | """tests whether the surface at the u,v parameter has been trimmed"""
269 | if self._classify_uv is None:
270 | self._classify_uv = BRepTopAdaptor_FClass2d(self, 1e-9)
271 | uv = gp_Pnt2d(u, v)
272 | if self._classify_uv.Perform(uv) == TopAbs_IN:
273 | return True
274 | else:
275 | return False
276 |
277 | def parameter_to_point(self, u, v):
278 | """returns the coordinate at u,v"""
279 | return self.surface.Value(u, v)
280 |
281 | def point_to_parameter(self, pt):
282 | """
283 | returns the uv value of a point on a surface
284 | @param pt:
285 | """
286 | sas = ShapeAnalysis_Surface(self.surface)
287 | uv = sas.ValueOfUV(pt, self.tolerance)
288 | return uv.Coord()
289 |
290 | def continuity_edge_face(self, edge, face):
291 | """
292 | compute the continuity between two faces at :edge:
293 |
294 | :param edge: an Edge or TopoDS_Edge from :face:
295 | :param face: a Face or TopoDS_Face
296 | :return: bool, GeomAbs_Shape if it has continuity, otherwise
297 | False, None
298 | """
299 | bt = BRep_Tool()
300 | if bt.HasContinuity(edge, self, face):
301 | continuity = bt.Continuity(edge, self, face)
302 | return True, continuity
303 | else:
304 | return False, None
305 |
306 | # ===========================================================================
307 | # Surface.project
308 | # project curve, point on face
309 | # ===========================================================================
310 |
311 | def project_vertex(self, pnt, tol=TOLERANCE):
312 | """projects self with a point, curve, edge, face, solid
313 | method wraps dealing with the various topologies
314 |
315 | if other is a point:
316 | returns uv, point
317 |
318 | """
319 | if isinstance(pnt, TopoDS_Vertex):
320 | pnt = BRep_Tool.Pnt(pnt)
321 |
322 | proj = GeomAPI_ProjectPointOnSurf(pnt, self.surface, tol)
323 | uv = proj.LowerDistanceParameters()
324 | proj_pnt = proj.NearestPoint()
325 |
326 | return uv, proj_pnt
327 |
328 | def project_curve(self, other):
329 | # this way Geom_Circle and alike are valid too
330 | if (
331 | isinstance(other, TopoDS_Edge)
332 | or isinstance(other, Geom_Curve)
333 | or issubclass(other, Geom_Curve)
334 | ):
335 | # convert edge to curve
336 | first, last = topexp.FirstVertex(other), topexp.LastVertex(other)
337 | lbound, ubound = BRep_Tool().Parameter(first, other), BRep_Tool().Parameter(
338 | last, other
339 | )
340 | other = BRep_Tool.Curve(other, lbound, ubound)
341 | return geomprojlib.Project(other, self.surface)
342 |
343 | def project_edge(self, edg):
344 | if hasattr(edg, "adaptor"):
345 | return self.project_curve(self, self.adaptor)
346 | return self.project_curve(self, to_adaptor_3d(edg))
347 |
348 | def iso_curve(self, u_or_v, param):
349 | """
350 | get the iso curve from a u,v + parameter
351 | :param u_or_v:
352 | :param param:
353 | :return:
354 | """
355 | uv = 0 if u_or_v == "u" else 1
356 | iso = Adaptor3d_IsoCurve(self.adaptor, uv, param)
357 | return iso
358 |
359 | def edges(self):
360 | return [Edge(i) for i in WireExplorer(next(self.topo.wires())).ordered_edges()]
361 |
362 | def __repr__(self):
363 | return self.name
364 |
365 | def __str__(self):
366 | return self.__repr__()
367 |
368 |
369 | if __name__ == "__main__":
370 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere
371 |
372 | sph = BRepPrimAPI_MakeSphere(1, 1).Face()
373 | fc = Face(sph)
374 | print(fc.is_trimmed())
375 | print(fc.is_planar())
376 |
--------------------------------------------------------------------------------
/OCCUtils/shell.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.TopoDS import TopoDS_Shell
19 | from OCC.Core.ShapeAnalysis import ShapeAnalysis_Shell
20 |
21 | from OCCUtils.Topology import Topo
22 | from OCCUtils.base import BaseObject, GlobalProperties
23 |
24 |
25 | class Shell(TopoDS_Shell, BaseObject):
26 | _n = 0
27 |
28 | def __init__(self, shell):
29 | assert isinstance(shell, TopoDS_Shell), (
30 | "need a TopoDS_Shell, got a %s" % shell.__class__
31 | )
32 | assert not shell.IsNull()
33 | super(Shell, self).__init__()
34 | BaseObject.__init__(self, "shell")
35 | # we need to copy the base shape using the following three
36 | # lines
37 | assert self.IsNull()
38 | self.TShape(shell.TShape())
39 | self.Location(shell.Location())
40 | self.Orientation(shell.Orientation())
41 | assert not self.IsNull()
42 |
43 | self.GlobalProperties = GlobalProperties(self)
44 | self._n += 1
45 |
46 | def analyse(self):
47 | """
48 |
49 | :return:
50 | """
51 | ss = ShapeAnalysis_Shell(self)
52 | if ss.HasFreeEdges():
53 | bad_edges = [e for e in Topo(ss.BadEdges()).edges()]
54 | return bad_edges
55 |
56 | def Faces(self):
57 | """
58 |
59 | :return:
60 | """
61 | return Topo(self, True).faces()
62 |
63 | def Wires(self):
64 | """
65 | :return:
66 | """
67 | return Topo(self, True).wires()
68 |
69 | def Edges(self):
70 | return Topo(self, True).edges()
71 |
--------------------------------------------------------------------------------
/OCCUtils/solid.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2013 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.TopoDS import TopoDS_Solid
19 |
20 | from OCCUtils.Topology import Topo
21 | from OCCUtils.base import GlobalProperties, BaseObject
22 | from OCCUtils.shell import Shell
23 |
24 |
25 | class Solid(TopoDS_Solid, BaseObject):
26 | def __init__(self, solid):
27 | assert isinstance(solid, TopoDS_Solid), (
28 | "need a TopoDS_Solid, got a %s" % solid.__class__
29 | )
30 | assert not solid.IsNull()
31 | super(Solid, self).__init__()
32 | BaseObject.__init__(self, "solid")
33 | # we need to copy the base shape using the following three
34 | # lines
35 | assert self.IsNull()
36 | self.TShape(solid.TShape())
37 | self.Location(solid.Location())
38 | self.Orientation(solid.Orientation())
39 | assert not self.IsNull()
40 |
41 | self.GlobalProperties = GlobalProperties(self)
42 |
43 | def shells(self):
44 | return (Shell(sh) for sh in Topo(self))
45 |
--------------------------------------------------------------------------------
/OCCUtils/types_lut.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.BRepCheck import *
19 | from OCC.Core.GeomAbs import *
20 | from OCC.Core.TopoDS import topods, TopoDS_Shape
21 | from OCC.Core.BRep import BRep_Tool_Surface
22 | from OCC.Core.TopAbs import *
23 | from OCC.Core.Geom import Geom_CylindricalSurface, Geom_Plane
24 |
25 |
26 | class ShapeToTopology(object):
27 | """
28 | looks up the topology type and returns the corresponding topological entity
29 | """
30 |
31 | def __init__(self):
32 | self.topoTypes = {
33 | TopAbs_VERTEX: topods.Vertex,
34 | TopAbs_EDGE: topods.Edge,
35 | TopAbs_FACE: topods.Face,
36 | TopAbs_WIRE: topods.Wire,
37 | TopAbs_SHELL: topods.Shell,
38 | TopAbs_SOLID: topods.Solid,
39 | TopAbs_COMPOUND: topods.Compound,
40 | TopAbs_COMPSOLID: topods.CompSolid,
41 | }
42 |
43 | def __call__(self, shape):
44 | if isinstance(shape, TopoDS_Shape):
45 | return self.topoTypes[shape.ShapeType()](shape)
46 | else:
47 | raise AttributeError("shape has not method `ShapeType`")
48 |
49 | def __getitem__(self, item):
50 | return self(item)
51 |
52 |
53 | class EnumLookup(object):
54 | """
55 | perform bi-directional lookup of Enums'...
56 | """
57 |
58 | def __init__(self, li_in, li_out):
59 | self.d = {}
60 | for a, b in zip(li_in, li_out):
61 | self.d[a] = b
62 | self.d[b] = a
63 |
64 | def __getitem__(self, item):
65 | return self.d[item]
66 |
67 |
68 | _curve_typesA = (
69 | GeomAbs_Line,
70 | GeomAbs_Circle,
71 | GeomAbs_Ellipse,
72 | GeomAbs_Hyperbola,
73 | GeomAbs_Parabola,
74 | GeomAbs_BezierCurve,
75 | GeomAbs_BSplineCurve,
76 | GeomAbs_OtherCurve,
77 | )
78 | _curve_typesB = (
79 | "line",
80 | "circle",
81 | "ellipse",
82 | "hyperbola",
83 | "parabola",
84 | "bezier",
85 | "spline",
86 | "other",
87 | )
88 | _surface_typesA = (
89 | GeomAbs_Plane,
90 | GeomAbs_Cylinder,
91 | GeomAbs_Cone,
92 | GeomAbs_Sphere,
93 | GeomAbs_Torus,
94 | GeomAbs_BezierSurface,
95 | GeomAbs_BSplineSurface,
96 | GeomAbs_SurfaceOfRevolution,
97 | GeomAbs_SurfaceOfExtrusion,
98 | GeomAbs_OffsetSurface,
99 | GeomAbs_OtherSurface,
100 | )
101 | _surface_typesB = (
102 | "plane",
103 | "cylinder",
104 | "cone",
105 | "sphere",
106 | "torus",
107 | "bezier",
108 | "spline",
109 | "revolution",
110 | "extrusion",
111 | "offset",
112 | "other",
113 | )
114 |
115 |
116 | _stateA = ("in", "out", "on", "unknown")
117 | _stateB = (TopAbs_IN, TopAbs_OUT, TopAbs_ON, TopAbs_UNKNOWN)
118 |
119 |
120 | _orientA = ["TopAbs_FORWARD", "TopAbs_REVERSED", "TopAbs_INTERNAL", "TopAbs_EXTERNAL"]
121 | _orientB = [TopAbs_FORWARD, TopAbs_REVERSED, TopAbs_INTERNAL, TopAbs_EXTERNAL]
122 |
123 |
124 | _topoTypesA = [
125 | "vertex",
126 | "edge",
127 | "wire",
128 | "face",
129 | "shell",
130 | "solid",
131 | "compsolid",
132 | "compound",
133 | "shape",
134 | ]
135 | _topoTypesB = [
136 | TopAbs_VERTEX,
137 | TopAbs_EDGE,
138 | TopAbs_WIRE,
139 | TopAbs_FACE,
140 | TopAbs_SHELL,
141 | TopAbs_SOLID,
142 | TopAbs_COMPSOLID,
143 | TopAbs_COMPOUND,
144 | TopAbs_SHAPE,
145 | ]
146 |
147 |
148 | _geom_types_a = [
149 | "line",
150 | "circle",
151 | "ellipse",
152 | "hyperbola",
153 | "parabola",
154 | "beziercurve",
155 | "bsplinecurve",
156 | "othercurve",
157 | ]
158 | _geom_types_b = [
159 | GeomAbs_Line,
160 | GeomAbs_Circle,
161 | GeomAbs_Ellipse,
162 | GeomAbs_Hyperbola,
163 | GeomAbs_Parabola,
164 | GeomAbs_BezierCurve,
165 | GeomAbs_BSplineCurve,
166 | GeomAbs_OtherCurve,
167 | ]
168 |
169 |
170 | # TODO: make a function that generalizes this, there is absolutely
171 | # no need for 2 lists to define an EnumLookup
172 |
173 |
174 | def fix_formatting(_str):
175 | return [i.strip() for i in _str.split(",")]
176 |
177 |
178 | _brep_check_a = fix_formatting(
179 | "NoError, InvalidPointOnCurve,\
180 | InvalidPointOnCurveOnSurface, InvalidPointOnSurface,\
181 | No3DCurve, Multiple3DCurve, Invalid3DCurve, NoCurveOnSurface,\
182 | InvalidCurveOnSurface, InvalidCurveOnClosedSurface, InvalidSameRangeFlag,\
183 | InvalidSameParameterFlag,\
184 | InvalidDegeneratedFlag, FreeEdge, InvalidMultiConnexity, InvalidRange,\
185 | EmptyWire, RedundantEdge, SelfIntersectingWire, NoSurface,\
186 | InvalidWire, RedundantWire, IntersectingWires, InvalidImbricationOfWires,\
187 | EmptyShell, RedundantFace, UnorientableShape, NotClosed,\
188 | NotConnected, SubshapeNotInShape, BadOrientation, BadOrientationOfSubshape,\
189 | InvalidToleranceValue, CheckFail"
190 | )
191 |
192 | _brep_check_b = [
193 | BRepCheck_NoError,
194 | BRepCheck_InvalidPointOnCurve,
195 | BRepCheck_InvalidPointOnCurveOnSurface,
196 | BRepCheck_InvalidPointOnSurface,
197 | BRepCheck_No3DCurve,
198 | BRepCheck_Multiple3DCurve,
199 | BRepCheck_Invalid3DCurve,
200 | BRepCheck_NoCurveOnSurface,
201 | BRepCheck_InvalidCurveOnSurface,
202 | BRepCheck_InvalidCurveOnClosedSurface,
203 | BRepCheck_InvalidSameRangeFlag,
204 | BRepCheck_InvalidSameParameterFlag,
205 | BRepCheck_InvalidDegeneratedFlag,
206 | BRepCheck_FreeEdge,
207 | BRepCheck_InvalidMultiConnexity,
208 | BRepCheck_InvalidRange,
209 | BRepCheck_EmptyWire,
210 | BRepCheck_RedundantEdge,
211 | BRepCheck_SelfIntersectingWire,
212 | BRepCheck_NoSurface,
213 | BRepCheck_InvalidWire,
214 | BRepCheck_RedundantWire,
215 | BRepCheck_IntersectingWires,
216 | BRepCheck_InvalidImbricationOfWires,
217 | BRepCheck_EmptyShell,
218 | BRepCheck_RedundantFace,
219 | BRepCheck_UnorientableShape,
220 | BRepCheck_NotClosed,
221 | BRepCheck_NotConnected,
222 | BRepCheck_SubshapeNotInShape,
223 | BRepCheck_BadOrientation,
224 | BRepCheck_BadOrientationOfSubshape,
225 | BRepCheck_InvalidToleranceValue,
226 | BRepCheck_CheckFail,
227 | ]
228 |
229 | brepcheck_lut = EnumLookup(_brep_check_a, _brep_check_b)
230 | curve_lut = EnumLookup(_curve_typesA, _curve_typesB)
231 | surface_lut = EnumLookup(_surface_typesA, _surface_typesB)
232 | state_lut = EnumLookup(_stateA, _stateB)
233 | orient_lut = EnumLookup(_orientA, _orientB)
234 | topo_lut = EnumLookup(_topoTypesA, _topoTypesB)
235 | shape_lut = ShapeToTopology()
236 | geom_lut = EnumLookup(_geom_types_a, _geom_types_b)
237 |
238 | # todo: refactor, these classes have been moved from the "Topology" directory
239 | # which had too many overlapping methods & classes, that are
240 | # now part of the KBE module...
241 | # still need to think what to do with these...
242 | # what_is_face should surely become a lut [ geom_lut? ]
243 | # i'm not sure whether casting to a gp_* is useful...
244 |
245 | classes = dir()
246 | geom_classes = []
247 | for elem in classes:
248 | if elem.startswith("Geom") and not "swig" in elem:
249 | geom_classes.append(elem)
250 |
251 |
252 | def what_is_face(face):
253 | """Returns all class names for which this class can be downcasted"""
254 | if not face.ShapeType() == TopAbs_FACE:
255 | print("%s is not a TopAbs_FACE. Conversion impossible")
256 | return None
257 | hs = BRep_Tool_Surface(face)
258 | obj = hs.GetObject()
259 | result = []
260 | for elem in classes:
261 | if elem.startswith("Geom") and not "swig" in elem:
262 | geom_classes.append(elem)
263 | # Run the test for each class
264 | for geom_class in geom_classes:
265 | if obj.IsKind(geom_class) and not geom_class in result:
266 | result.append(geom_class)
267 | return result
268 |
269 |
270 | def face_is_plane(face):
271 | """Returns True if the TopoDS_Shape is a plane, False otherwise"""
272 | hs = BRep_Tool_Surface(face)
273 | downcast_result = Geom_Plane().DownCast(hs)
274 | # the handle is null if downcast failed or is not possible,
275 | # that is to say the face is not a plane
276 | if downcast_result.IsNull():
277 | return False
278 | else:
279 | return True
280 |
281 |
282 | def shape_is_cylinder(face):
283 | """Returns True is the TopoDS_Shape is a cylinder, False otherwise"""
284 | hs = BRep_Tool_Surface(face)
285 | downcast_result = Geom_CylindricalSurface().DownCast(hs)
286 | if downcast_result.IsNull():
287 | return False
288 | else:
289 | return True
290 |
--------------------------------------------------------------------------------
/OCCUtils/vertex.py:
--------------------------------------------------------------------------------
1 | ##Copyright 2008-2013 Jelle Feringa (jelleferinga@gmail.com)
2 | ##
3 | ##This file is part of pythonOCC.
4 | ##
5 | ##pythonOCC is free software: you can redistribute it and/or modify
6 | ##it under the terms of the GNU Lesser General Public License as published by
7 | ##the Free Software Foundation, either version 3 of the License, or
8 | ##(at your option) any later version.
9 | ##
10 | ##pythonOCC is distributed in the hope that it will be useful,
11 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ##GNU Lesser General Public License for more details.
14 | ##
15 | ##You should have received a copy of the GNU Lesser General Public License
16 | ##along with pythonOCC. If not, see
17 |
18 | from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Dir, gp_XYZ, gp_Pnt2d
19 | from OCC.Core.TopoDS import TopoDS_Vertex
20 | from OCC.Core.ShapeBuild import ShapeBuild_ReShape
21 |
22 | from OCCUtils.base import BaseObject
23 | from OCCUtils.Construct import make_vertex
24 |
25 |
26 | class Vertex(TopoDS_Vertex, BaseObject):
27 | """
28 | wraps gp_Pnt
29 | """
30 |
31 | _n = 0
32 |
33 | def __init__(self, x, y, z):
34 | super(Vertex, self).__init__()
35 | """Constructor for KbeVertex"""
36 | BaseObject.__init__(self, name="Vertex #{0}".format(self._n))
37 |
38 | self._n += 1 # should be a property of KbeObject
39 | self._pnt = gp_Pnt(x, y, z)
40 | self._vertex = make_vertex(self._pnt)
41 | TopoDS_Vertex.__init__(self, self._vertex)
42 |
43 | def _update(self):
44 | """ """
45 | # TODO: perhaps should take an argument until which topological level
46 | # topological entities bound to the vertex should be updated too...
47 | reshape = ShapeBuild_ReShape()
48 | reshape.Replace(self._vertex, make_vertex(self._pnt))
49 |
50 | @staticmethod
51 | def from_pnt(cls, pnt):
52 | x, y, z = pnt.X(), pnt.Y(), pnt.Z()
53 | return cls(x, y, z)
54 |
55 | @property
56 | def x(self):
57 | return self._pnt.X()
58 |
59 | @x.setter
60 | def x(self, val):
61 | self._pnt.SetX(val)
62 | self._update()
63 |
64 | @property
65 | def y(self):
66 | return self._pnt.Y()
67 |
68 | @y.setter
69 | def y(self, val):
70 | self._pnt.SetY(val)
71 | self._update()
72 |
73 | @property
74 | def z(self):
75 | return self._pnt.Z()
76 |
77 | @z.setter
78 | def z(self, val):
79 | self._pnt.SetZ(val)
80 | self._update()
81 |
82 | @property
83 | def xyz(self):
84 | return self._pnt.Coord()
85 |
86 | @xyz.setter
87 | def xyz(self, *val):
88 | self._pnt.SetXYZ(*val)
89 | self._update()
90 |
91 | def __repr__(self):
92 | return self.name
93 |
94 | @property
95 | def as_vec(self):
96 | """returns a gp_Vec version of self"""
97 | return gp_Vec(*self._pnt.Coord())
98 |
99 | @property
100 | def as_dir(self):
101 | """returns a gp_Dir version of self"""
102 | return gp_Dir(*self._pnt.Coord())
103 |
104 | @property
105 | def as_xyz(self):
106 | """returns a gp_XYZ version of self"""
107 | return gp_XYZ(*self._pnt.Coord())
108 |
109 | @property
110 | def as_pnt(self):
111 | return self._pnt
112 |
113 | @property
114 | def as_2d(self):
115 | """returns a gp_Pnt2d version of self"""
116 | return gp_Pnt2d(*self._pnt.Coord()[:2])
117 |
--------------------------------------------------------------------------------
/OCCUtils/wire.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2008-2013 Jelle Feringa (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see
19 |
20 | from OCC.Core.TopoDS import TopoDS_Wire
21 |
22 | from OCCUtils.base import BaseObject
23 |
24 |
25 | class Wire(TopoDS_Wire, BaseObject):
26 | def __init__(self, wire):
27 | """ """
28 | assert isinstance(wire, TopoDS_Wire), (
29 | "need a TopoDS_Wire, got a %s" % wire.__class__
30 | )
31 | assert not wire.IsNull()
32 | super(Wire, self).__init__()
33 | BaseObject.__init__(self, "wire")
34 | # we need to copy the base shape using the following three
35 | # lines
36 | assert self.IsNull()
37 | self.TShape(wire.TShape())
38 | self.Location(wire.Location())
39 | self.Orientation(wire.Orientation())
40 | assert not self.IsNull()
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/tpaviot/pythonocc-utils)
2 |
3 | # pythonocc-utils
4 | A python package that provides useful classes/methods for pythonocc
5 |
--------------------------------------------------------------------------------
/doc/README:
--------------------------------------------------------------------------------
1 | insert documentation here, Sphinx formatted
2 |
--------------------------------------------------------------------------------
/examples/README:
--------------------------------------------------------------------------------
1 | insert examples in this folder
2 |
--------------------------------------------------------------------------------
/examples/curve_geom_plate.igs:
--------------------------------------------------------------------------------
1 | S 1
2 | 1H,,1H;,, G 1
3 | 70H/Users/localadmin/SVN/pythonocc/src/samples/data/curves_geom_plate.igG 2
4 | s, G 3
5 | 26HRhinoceros ( Feb 28 2010 ),31HTrout Lake IGES 012 Feb 28 2010, G 4
6 | 32,38,6,308,15, G 5
7 | , G 6
8 | 1.0D0,2,2HMM,1,0.254D0,13H100303.075003, G 7
9 | 0.001D0, G 8
10 | 22.8014214254268D0, G 9
11 | , G 10
12 | , G 11
13 | 10,0,13H100303.075003; G 12
14 | 314 1 0 0 0 0 0 000000200D 1
15 | 314 0 1 1 0 0 0 COLOR 0D 2
16 | 406 2 0 0 1 0 0 000000300D 3
17 | 406 0 -1 1 3 0 0LEVELDEF 0D 4
18 | 126 3 0 0 1 0 0 000000000D 5
19 | 126 0 -1 10 0 0 03d BsCrv 0D 6
20 | 126 13 0 0 1 0 0 000000000D 7
21 | 126 0 -1 9 0 0 03d BsCrv 0D 8
22 | 126 22 0 0 1 0 0 000000000D 9
23 | 126 0 -1 9 0 0 03d BsCrv 0D 10
24 | 126 31 0 0 1 0 0 000000000D 11
25 | 126 0 -1 9 0 0 03d BsCrv 0D 12
26 | 126 40 0 0 1 0 0 000000000D 13
27 | 126 0 -1 12 0 0 03d BsCrv 0D 14
28 | 126 52 0 0 1 0 0 000000000D 15
29 | 126 0 -1 11 0 0 03d BsCrv 0D 16
30 | 314,0.0,0.0,0.0,20HRGB( 0, 0, 0 ); 0000001P 1
31 | 406,2,1,7HDefault; 0000003P 2
32 | 126,5,3,0,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,6.696836136295956D0, 0000005P 3
33 | 12.53004301528411D0,16.48566235018374D0,16.48566235018374D0, 0000005P 4
34 | 16.48566235018374D0,16.48566235018374D0,1.0D0,1.0D0,1.0D0,1.0D0, 0000005P 5
35 | 1.0D0,1.0D0,12.05104335564882D0,6.511168855975367D0,0.0D0, 0000005P 6
36 | 10.01636949950848D0,5.592925670524023D0,0.0D0, 0000005P 7
37 | 5.197894335857237D0,2.562740525908483D0,0.0D0, 0000005P 8
38 | 9.728591342963917D0,-3.664998927002043D0,-3.840386244052771D0, 0000005P 9
39 | 6.432714845727936D0,-6.019037116461988D0,0.0D0, 0000005P 10
40 | 5.270516142678511D0,-6.641806192114689D0,0.0D0,0.0D0, 0000005P 11
41 | 16.48566235018374D0,0.0D0,0.0D0,0.0D0; 0000005P 12
42 | 126,4,3,0,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,6.999864493298351D0, 0000007P 13
43 | 8.859192764506915D0,8.859192764506915D0,8.859192764506915D0, 0000007P 14
44 | 8.859192764506915D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0, 0000007P 15
45 | 16.86488779624402D0,-2.438061664129213D0,0.0D0, 0000007P 16
46 | 15.33293866795034D0,-4.197995051107919D0,5.120749871007363D0, 0000007P 17
47 | 13.90163736740188D0,-7.50582374999729D0,5.120749871007363D0, 0000007P 18
48 | 15.07186028339268D0,-10.28186254162986D0,0.0D0, 0000007P 19
49 | 15.36648607675535D0,-10.82713132316834D0,0.0D0,0.0D0, 0000007P 20
50 | 8.859192764506915D0,0.0D0,0.0D0,0.0D0; 0000007P 21
51 | 126,4,3,0,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,3.364213325866694D0, 0000009P 22
52 | 8.562860070726298D0,8.562860070726298D0,8.562860070726298D0, 0000009P 23
53 | 8.562860070726298D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0, 0000009P 24
54 | 21.27157613451663D0,-1.867352103411764D0,0.0D0, 0000009P 25
55 | 21.7935035196958D0,-2.859894156444129D0,0.0D0, 0000009P 26
56 | 22.8014214254268D0,-5.576418461289208D0,-2.590142461137446D0, 0000009P 27
57 | 22.66091526673902D0,-8.547403533392577D0,1.291135775261908D0, 0000009P 28
58 | 22.24584679716624D0,-10.22984195782097D0,0.0D0,0.0D0, 0000009P 29
59 | 8.562860070726298D0,0.0D0,0.0D0,0.0D0; 0000009P 30
60 | 126,4,3,1,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,3.514658815987376D0, 0000011P 31
61 | 6.425040462720305D0,6.425040462720305D0,6.425040462720305D0, 0000011P 32
62 | 6.425040462720305D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0, 0000011P 33
63 | -7.550597635534528D0,3.32875809738619D0,0.0D0, 0000011P 34
64 | -7.966521593812645D0,2.233521342607157D0,0.0D0, 0000011P 35
65 | -8.303386617591585D0,-0.01688405686600536D0,0.0D0, 0000011P 36
66 | -7.375281959406379D0,-2.046203544371695D0,0.0D0, 0000011P 37
67 | -6.819894638547317D0,-2.84162276606138D0,0.0D0,0.0D0, 0000011P 38
68 | 6.425040462720305D0,0.0D0,0.0D0,1.0D0; 0000011P 39
69 | 126,6,3,1,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,12.07783483008546D0, 0000013P 40
70 | 20.17439919842325D0,30.52403486586361D0,32.06449204159077D0, 0000013P 41
71 | 32.06449204159077D0,32.06449204159077D0,32.06449204159077D0, 0000013P 42
72 | 1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,-6.819894638547317D0, 0000013P 43
73 | -2.84162276606138D0,0.0D0,-2.795097814053425D0, 0000013P 44
74 | -2.937764372686643D0,0.0D0,4.624722172595563D0, 0000013P 45
75 | -3.493606652593725D0,0.0D0,10.36809536963595D0, 0000013P 46
76 | -15.17148042966633D0,0.0D0,18.01848597367706D0, 0000013P 47
77 | -8.549512576461879D0,0.0D0,21.7699233043508D0, 0000013P 48
78 | -10.03706120407712D0,0.0D0,22.24584679716624D0, 0000013P 49
79 | -10.22984195782097D0,0.0D0,0.0D0,32.06449204159077D0,0.0D0, 0000013P 50
80 | 0.0D0,1.0D0; 0000013P 51
81 | 126,6,3,1,0,1,0,0.0D0,0.0D0,0.0D0,0.0D0,9.748455833399156D0, 0000015P 52
82 | 21.38148598816612D0,28.50997364601394D0,34.51852461646787D0, 0000015P 53
83 | 34.51852461646787D0,34.51852461646787D0,34.51852461646787D0, 0000015P 54
84 | 1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,1.0D0,-7.550597635534528D0, 0000015P 55
85 | 3.32875809738619D0,0.0D0,-4.56014000187896D0,4.60010294122214D0, 0000015P 56
86 | 0.0D0,1.469796864585584D0,5.586685561838665D0,0.0D0, 0000015P 57
87 | 15.46939590361923D0,9.120943074616369D0,0.0D0, 0000015P 58
88 | 13.19602773094833D0,-3.48300548185671D0,0.0D0, 0000015P 59
89 | 19.36132569072579D0,-2.469309460937569D0,0.0D0, 0000015P 60
90 | 21.27157613451663D0,-1.867352103411764D0,0.0D0,0.0D0, 0000015P 61
91 | 34.51852461646787D0,0.0D0,0.0D0,1.0D0; 0000015P 62
92 | S0000001G0000012D0000016P0000062 T 1
93 |
--------------------------------------------------------------------------------
/examples/occutils_geomplate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2009-2015 Jelle Ferina (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 |
21 | # TODO:
22 | # * need examples where the tangency to constraining faces is respected
23 |
24 | from __future__ import print_function
25 |
26 | import os
27 | import types
28 | import sys
29 | import time
30 |
31 | from OCC.Core.gp import gp_Pnt
32 | from OCC.Core.BRepAdaptor import BRepAdaptor_Curve
33 | from OCC.Core.BRep import BRep_Tool
34 | from OCC.Core.ShapeAnalysis import ShapeAnalysis_Surface
35 | from OCC.Core.GeomLProp import GeomLProp_SLProps
36 | from OCC.Core.BRepFill import BRepFill_CurveConstraint
37 | from OCC.Core.GeomPlate import (GeomPlate_MakeApprox,
38 | GeomPlate_BuildPlateSurface,
39 | GeomPlate_PointConstraint)
40 | from OCC.Core.IGESControl import IGESControl_Reader
41 | from OCC.Core.IFSelect import (IFSelect_RetDone,
42 | IFSelect_ItemsByEntity)
43 | from OCC.Display.SimpleGui import init_display
44 | from OCC.Core.TopoDS import TopoDS_Compound
45 | from OCC.Core.BRep import BRep_Builder
46 | display, start_display, add_menu, add_function_to_menu = init_display()
47 |
48 |
49 | from OCCUtils.Construct import (make_closed_polygon, make_n_sided,
50 | make_vertex, make_face)
51 | from OCCUtils.Topology import WireExplorer, Topo
52 |
53 | try:
54 | from scipy import arange
55 | from scipy.optimize import fsolve
56 | HAVE_SCIPY = True
57 | except ImportError:
58 | print('scipy not installed, will not be able to run the geomplate example')
59 | HAVE_SCIPY = False
60 |
61 |
62 | class IGESImporter(object):
63 | def __init__(self, filename=None):
64 | self._shapes = []
65 | self.nbs = 0
66 | if not os.path.isfile(filename):
67 | raise AssertionError("IGESImporter initialization Error: file %s not found." % filename)
68 | self.set_filename(filename)
69 |
70 | def set_filename(self, filename):
71 | if not os.path.isfile(filename):
72 | raise AssertionError("IGESImporter initialization Error: file %s not found." % filename)
73 | else:
74 | self._filename = filename
75 |
76 | def read_file(self):
77 | """
78 | Read the IGES file and stores the result in a list of TopoDS_Shape
79 | """
80 | aReader = IGESControl_Reader()
81 | status = aReader.ReadFile(self._filename)
82 | if status == IFSelect_RetDone:
83 | failsonly = False
84 | aReader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
85 | nbr = aReader.NbRootsForTransfer()
86 | aReader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
87 | # ok = aReader.TransferRoots()
88 | for n in range(1, nbr+1):
89 | self.nbs = aReader.NbShapes()
90 | if self.nbs == 0:
91 | print("At least one shape in IGES cannot be transfered")
92 | elif nbr == 1 and self.nbs == 1:
93 | aResShape = aReader.Shape(1)
94 | if aResShape.IsNull():
95 | print("At least one shape in IGES cannot be transferred")
96 | self._shapes.append(aResShape)
97 | else:
98 | for i in range(1, self.nbs+1):
99 | aShape = aReader.Shape(i)
100 | if aShape.IsNull():
101 | print("At least one shape in STEP cannot be transferred")
102 | else:
103 | self._shapes.append(aShape)
104 | return True
105 | else:
106 | print("Error: can't read file %s" % self._filename)
107 | return False
108 | return False
109 |
110 | def get_compound(self):
111 | """ Create and returns a compound from the _shapes list
112 | """
113 | # Create a compound
114 | compound = TopoDS_Compound()
115 | B = BRep_Builder()
116 | B.MakeCompound(compound)
117 | # Populate the compound
118 | for shape in self._shapes:
119 | B.Add(compound, shape)
120 | return compound
121 |
122 | def get_shapes(self):
123 | return self._shapes
124 |
125 |
126 | def geom_plate(event=None):
127 | display.EraseAll()
128 | p1 = gp_Pnt(0, 0, 0)
129 | p2 = gp_Pnt(0, 10, 0)
130 | p3 = gp_Pnt(0, 10, 10)
131 | p4 = gp_Pnt(0, 0, 10)
132 | p5 = gp_Pnt(5, 5, 5)
133 | poly = make_closed_polygon([p1, p2, p3, p4])
134 | edges = [i for i in Topo(poly).edges()]
135 | face = make_n_sided(edges, [p5])
136 | display.DisplayShape(edges)
137 | display.DisplayShape(make_vertex(p5))
138 | display.DisplayShape(face, update=True)
139 |
140 | #============================================================================
141 | # Find a surface such that the radius at the vertex is n
142 | #============================================================================
143 |
144 |
145 | def build_plate(polygon, points):
146 | '''
147 | build a surface from a constraining polygon(s) and point(s)
148 | @param polygon: list of polygons ( TopoDS_Shape)
149 | @param points: list of points ( gp_Pnt )
150 | '''
151 | # plate surface
152 | bpSrf = GeomPlate_BuildPlateSurface(3, 15, 2)
153 |
154 | # add curve constraints
155 | for poly in polygon:
156 | for edg in WireExplorer(poly).ordered_edges():
157 | c = BRepAdaptor_Curve()
158 | c.ChangeCurve().Initialize(edg)
159 | constraint = BRepFill_CurveConstraint(c.GetHandle(), 0)
160 | bpSrf.Add(constraint.GetHandle())
161 |
162 | # add point constraint
163 | for pt in points:
164 | bpSrf.Add(GeomPlate_PointConstraint(pt, 0).GetHandle())
165 | bpSrf.Perform()
166 |
167 | maxSeg, maxDeg, critOrder = 9, 8, 0
168 | tol = 1e-4
169 | dmax = max([tol, 10*bpSrf.G0Error()])
170 |
171 | srf = bpSrf.Surface()
172 | plate = GeomPlate_MakeApprox(srf, tol, maxSeg, maxDeg, dmax, critOrder)
173 | uMin, uMax, vMin, vMax = srf.GetObject().Bounds()
174 |
175 | return make_face(plate.Surface(), uMin, uMax, vMin, vMax, 1e-4)
176 |
177 |
178 | def radius_at_uv(face, u, v):
179 | '''
180 | returns the mean radius at a u,v coordinate
181 | @param face: surface input
182 | @param u,v: u,v coordinate
183 | '''
184 | h_srf = BRep_Tool().Surface(face)
185 | # uv_domain = GeomLProp_SurfaceTool().Bounds(h_srf)
186 | curvature = GeomLProp_SLProps(h_srf, u, v, 1, 1e-6)
187 | try:
188 | _crv_min = 1./curvature.MinCurvature()
189 | except ZeroDivisionError:
190 | _crv_min = 0.
191 |
192 | try:
193 | _crv_max = 1./curvature.MaxCurvature()
194 | except ZeroDivisionError:
195 | _crv_max = 0.
196 | return abs((_crv_min+_crv_max)/2.)
197 |
198 |
199 | def uv_from_projected_point_on_face(face, pt):
200 | '''
201 | returns the uv coordinate from a projected point on a face
202 | '''
203 | srf = BRep_Tool().Surface(face)
204 | sas = ShapeAnalysis_Surface(srf)
205 | uv = sas.ValueOfUV(pt, 1e-2)
206 | print('distance', sas.Value(uv).Distance(pt))
207 | return uv.Coord()
208 |
209 |
210 | class RadiusConstrainedSurface(object):
211 | '''
212 | returns a surface that has `radius` at `pt`
213 | '''
214 | def __init__(self, display, poly, pnt, targetRadius):
215 | self.display = display
216 | self.targetRadius = targetRadius
217 | self.poly = poly
218 | self.pnt = pnt
219 | self.plate = self.build_surface()
220 |
221 | def build_surface(self):
222 | '''
223 | builds and renders the plate
224 | '''
225 | self.plate = build_plate([self.poly], [self.pnt])
226 | self.display.EraseAll()
227 | self.display.DisplayShape(self.plate)
228 | vert = make_vertex(self.pnt)
229 | self.display.DisplayShape(vert, update=True)
230 |
231 | def radius(self, z):
232 | '''
233 | sets the height of the point constraining the plate, returns
234 | the radius at this point
235 | '''
236 | if isinstance(z, types.FloatType):
237 | self.pnt.SetX(z)
238 | else:
239 | self.pnt.SetX(float(z[0]))
240 | self.build_surface()
241 | uv = uv_from_projected_point_on_face(self.plate, self.pnt)
242 | print(uv)
243 | radius = radius_at_uv(self.plate, uv.X(), uv.Y())
244 | print('z: %f radius: %f ' % (z, radius))
245 | self.curr_radius = radius
246 | return self.targetRadius-abs(radius)
247 |
248 | def solve(self):
249 | fsolve(self.radius, 1, maxfev=1000)
250 | return self.plate
251 |
252 |
253 | def solve_radius(event=None):
254 | display.EraseAll()
255 | p1 = gp_Pnt(0, 0, 0)
256 | p2 = gp_Pnt(0, 10, 0)
257 | p3 = gp_Pnt(0, 10, 10)
258 | p4 = gp_Pnt(0, 0, 10)
259 | p5 = gp_Pnt(5, 5, 5)
260 | poly = make_closed_polygon([p1, p2, p3, p4])
261 | for i in arange(0.1, 3., 0.2).tolist():
262 | rcs = RadiusConstrainedSurface(display, poly, p5, i)
263 | # face = rcs.solve()
264 | print('Goal: %s radius: %s' % (i, rcs.curr_radius))
265 | time.sleep(0.5)
266 |
267 |
268 | def build_geom_plate(edges):
269 | bpSrf = GeomPlate_BuildPlateSurface(3, 9, 12)
270 |
271 | # add curve constraints
272 | for edg in edges:
273 | c = BRepAdaptor_Curve()
274 | print('edge:', edg)
275 | c.ChangeCurve().Initialize(edg)
276 | constraint = BRepFill_CurveConstraint(c.GetHandle(), 0)
277 | bpSrf.Add(constraint.GetHandle())
278 |
279 | # add point constraint
280 | try:
281 | bpSrf.Perform()
282 | except RuntimeError:
283 | print('Failed to build the geom plate surface')
284 |
285 | maxSeg, maxDeg, critOrder = 9, 8, 0
286 |
287 | srf = bpSrf.Surface()
288 | plate = GeomPlate_MakeApprox(srf, 1e-04, 100, 9, 1e-03, 0)
289 |
290 | uMin, uMax, vMin, vMax = srf.GetObject().Bounds()
291 | face = make_face(plate.Surface(), uMin, uMax, vMin, vMax, 1e-6)
292 | return face
293 |
294 |
295 | def build_curve_network(event=None):
296 | '''
297 | mimic the curve network surfacing command from rhino
298 | '''
299 | print('Importing IGES file...', end='')
300 | iges = IGESImporter('./curve_geom_plate.igs')
301 | iges.read_file()
302 | iges_cpd = iges.get_compound()
303 | print('done.')
304 |
305 | print('Building geomplate...', end='')
306 | topo = Topo(iges_cpd)
307 | edges_list = list(topo.edges())
308 | face = build_geom_plate(edges_list)
309 | print('done.')
310 | display.EraseAll()
311 | display.DisplayShape(edges_list)
312 | display.DisplayShape(face)
313 | display.FitAll()
314 | print('Cutting out of edges...')
315 | # Make a wire from outer edges
316 | # _edges = [edges_list[2], edges_list[3], edges_list[4], edges_list[5]]
317 | # outer_wire = make_wire(_edges)
318 |
319 |
320 | def exit(event=None):
321 | sys.exit()
322 |
323 | if __name__ == "__main__":
324 | add_menu('geom plate')
325 | add_function_to_menu('geom plate', geom_plate)
326 | if HAVE_SCIPY:
327 | add_function_to_menu('geom plate', solve_radius)
328 | add_function_to_menu('geom plate', build_curve_network)
329 | add_function_to_menu('geom plate', exit)
330 | start_display()
331 |
--------------------------------------------------------------------------------
/examples/occutils_surfaces.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2009-2015 Jelle Ferina (jelleferinga@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | import sys
21 | from itertools import chain
22 |
23 | from OCC.Display.SimpleGui import init_display
24 |
25 | sys.path.append('..')
26 | from OCCUtils.Common import points_to_bspline
27 | from OCCUtils.Construct import gp_Pnt, make_edge, make_n_sided, make_vertex
28 |
29 | display, start_display, add_menu, add_function_to_menu = init_display()
30 |
31 |
32 | def n_sided_patch():
33 |
34 | # left
35 | pts1 = (gp_Pnt(0, 0, 0.0),
36 | gp_Pnt(0, 1, 0.3),
37 | gp_Pnt(0, 2, -0.3),
38 | gp_Pnt(0, 3, 0.15),
39 | gp_Pnt(0, 4, 0),
40 | )
41 | # front
42 | pts2 = (gp_Pnt(0, 0, 0.0),
43 | gp_Pnt(1, 0, -0.3),
44 | gp_Pnt(2, 0, 0.15),
45 | gp_Pnt(3, 0, 0),
46 | gp_Pnt(4, 0, 0),
47 | )
48 | # back
49 | pts3 = (gp_Pnt(0, 4, 0),
50 | gp_Pnt(1, 4, 0.3),
51 | gp_Pnt(2, 4, -0.15),
52 | gp_Pnt(3, 4, 0),
53 | gp_Pnt(4, 4, 1),
54 | )
55 | # right
56 | pts4 = (gp_Pnt(4, 0, 0),
57 | gp_Pnt(4, 1, 0),
58 | gp_Pnt(4, 2, 0.3),
59 | gp_Pnt(4, 3, -0.15),
60 | gp_Pnt(4, 4, 1),
61 | )
62 |
63 | spl1 = points_to_bspline(pts1)
64 | spl2 = points_to_bspline(pts2)
65 | spl3 = points_to_bspline(pts3)
66 | spl4 = points_to_bspline(pts4)
67 |
68 | edges = map(make_edge, [spl1, spl2, spl3, spl4])
69 | verts = map(make_vertex, chain(pts1, pts2, pts3, pts4))
70 | f1 = make_n_sided(edges, [])
71 |
72 | display.DisplayShape(edges)
73 | display.DisplayShape(verts)
74 | display.DisplayShape(f1, update=True)
75 |
76 | if __name__ == '__main__':
77 | n_sided_patch()
78 | start_display()
79 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys, glob
4 | from distutils.core import setup
5 |
6 | DESCRIPTION = (
7 | 'A set of utilies for pythonocc'
8 | )
9 |
10 |
11 | CLASSIFIERS = [
12 | 'Operating System :: OS Independent',
13 | 'Programming Language :: Python',
14 | 'Programming Language :: Python :: 2',
15 | 'Programming Language :: Python :: 3',
16 | 'License :: OSI Approved :: LGPL License',
17 | 'Development Status :: 5 - Production/Stable',
18 | 'Intended Audience :: Developers',
19 | 'Topic :: Software Development'
20 | ]
21 |
22 | setup(
23 | name = 'OCCUtils',
24 | version = '0.1-dev',
25 | author = 'Jelle Feringa',
26 | author_email = 'jferinga@gmail.com',
27 | url = 'https://github.com/tpaviot/pythonocc-utils',
28 | description = DESCRIPTION,
29 | long_description = open('README.md').read(),
30 | license = 'LGPLv3',
31 | platforms = 'Platform Independent',
32 | packages = ['OCCUtils'],
33 | keywords = 'pythonocc CAD',
34 | classifiers = CLASSIFIERS,
35 | requires = ['OCC'],
36 | package_data = { 'OCCUtils': ['README.md', 'doc/*.*', 'examples/*.*'], },
37 | )
38 |
--------------------------------------------------------------------------------
/test/occutils_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Copyright 2009-2015 Thomas Paviot (tpaviot@gmail.com)
4 | ##
5 | ##This file is part of pythonOCC.
6 | ##
7 | ##pythonOCC is free software: you can redistribute it and/or modify
8 | ##it under the terms of the GNU Lesser General Public License as published by
9 | ##the Free Software Foundation, either version 3 of the License, or
10 | ##(at your option) any later version.
11 | ##
12 | ##pythonOCC is distributed in the hope that it will be useful,
13 | ##but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ##GNU Lesser General Public License for more details.
16 | ##
17 | ##You should have received a copy of the GNU Lesser General Public License
18 | ##along with pythonOCC. If not, see .
19 |
20 | import unittest
21 | import sys
22 |
23 | sys.path.append('../')
24 | sys.path.append('../OCCUtils')
25 |
26 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeSphere
27 | from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Edge
28 |
29 | from Topology import Topo, WireExplorer
30 | from edge import Edge
31 | from face import Face
32 | from wire import Wire
33 | from vertex import Vertex
34 | from shell import Shell
35 | from solid import Solid
36 |
37 |
38 | def get_test_box_shape():
39 | return BRepPrimAPI_MakeBox(10, 20, 30).Shape()
40 |
41 |
42 | def get_test_sphere_shape():
43 | return BRepPrimAPI_MakeSphere(10.).Shape()
44 |
45 |
46 | class TestTopo(unittest.TestCase):
47 | def setUp(self):
48 | self.topo = Topo(get_test_box_shape())
49 | assert self.topo
50 |
51 | def test_loop_faces(self):
52 | i = 0
53 | for face in self.topo.faces():
54 | i += 1
55 | assert(isinstance(face, TopoDS_Face))
56 | assert(i == 6)
57 |
58 | def test_loop_edges(self):
59 | i = 0
60 | for face in self.topo.edges():
61 | i += 1
62 | assert(isinstance(face, TopoDS_Edge))
63 | assert(i == 12)
64 |
65 | def number_of_topological_entities(self):
66 | assert(self.topo.number_of_faces() == 6)
67 | assert(self.topo.number_of_edges() == 12)
68 | assert(self.topo.number_of_vertices() == 8)
69 | assert(self.topo.number_of_wires() == 6)
70 | assert(self.topo.number_of_solids() == 1)
71 | assert(self.topo.number_of_shells() == 1)
72 | assert(self.topo.number_of_compounds() == 0)
73 | assert(self.topo.number_of_comp_solids() == 0)
74 |
75 | def test_nested_iteration(self):
76 | '''check nested looping'''
77 | for f in self.topo.faces():
78 | for e in self.topo.edges():
79 | assert isinstance(f, TopoDS_Face)
80 | assert isinstance(e, TopoDS_Edge)
81 |
82 | def test_kept_reference(self):
83 | '''did we keep a reference after looping several time through a list
84 | of topological entities?'''
85 | _tmp = []
86 | _faces = [i for i in self.topo.faces()]
87 | for f in _faces:
88 | _tmp.append(0 == f.IsNull())
89 | for f in _faces:
90 | _tmp.append(0 == f.IsNull())
91 | self.assertTrue(all(_tmp))
92 |
93 | def test_edge_face(self):
94 | edg = next(self.topo.edges())
95 | face = next(self.topo.faces())
96 | faces_from_edge = [i for i in self.topo.faces_from_edge(edg)]
97 | self.assertTrue(len(faces_from_edge) == self.topo.number_of_faces_from_edge(edg))
98 | edges_from_face = [i for i in self.topo.edges_from_face(face)]
99 | self.assertTrue(len(edges_from_face) == self.topo.number_of_edges_from_face(face))
100 |
101 | def test_edge_wire(self):
102 | edg = next(self.topo.edges())
103 | wire = next(self.topo.wires())
104 | wires_from_edge = [i for i in self.topo.wires_from_edge(edg)]
105 | self.assertTrue(len(wires_from_edge) == self.topo.number_of_wires_from_edge(edg))
106 | edges_from_wire = [i for i in self.topo.edges_from_wire(wire)]
107 | self.assertTrue(len(edges_from_wire) == self.topo.number_of_edges_from_wire(wire))
108 |
109 | def test_vertex_edge(self):
110 | vert = next(self.topo.vertices())
111 | edge = next(self.topo.edges())
112 | verts_from_edge = [i for i in self.topo.vertices_from_edge(edge)]
113 | self.assertTrue(len(verts_from_edge) == self.topo.number_of_vertices_from_edge(edge))
114 | edges_from_vert = [i for i in self.topo.edges_from_vertex(vert)]
115 | self.assertTrue(len(edges_from_vert) == self.topo.number_of_edges_from_vertex(vert))
116 |
117 | def test_vertex_face(self):
118 | vert = next(self.topo.vertices())
119 | face = next(self.topo.faces())
120 | faces_from_vertex = [i for i in self.topo.faces_from_vertex(vert)]
121 | self.assertTrue(len(faces_from_vertex) == self.topo.number_of_faces_from_vertex(vert))
122 | verts_from_face = [i for i in self.topo.vertices_from_face(face)]
123 | self.assertTrue(len(verts_from_face) == self.topo.number_of_vertices_from_face(face))
124 |
125 | def test_face_solid(self):
126 | face = next(self.topo.faces())
127 | solid = next(self.topo.solids())
128 | faces_from_solid = [i for i in self.topo.faces_from_solids(solid)]
129 | self.assertTrue(len(faces_from_solid) == self.topo.number_of_faces_from_solids(solid))
130 | solids_from_face = [i for i in self.topo.solids_from_face(face)]
131 | self.assertTrue(len(solids_from_face) == self.topo.number_of_solids_from_face(face))
132 |
133 | def test_wire_face(self):
134 | wire = next(self.topo.wires())
135 | face = next(self.topo.faces())
136 | faces_from_wire = [i for i in self.topo.faces_from_wire(wire)]
137 | self.assertTrue(len(faces_from_wire) == self.topo.number_of_faces_from_wires(wire))
138 | wires_from_face = [i for i in self.topo.wires_from_face(face)]
139 | self.assertTrue(len(wires_from_face) == self.topo.number_of_wires_from_face(face))
140 |
141 | def test_edges_out_of_scope(self):
142 | # check pointers going out of scope
143 | face = next(self.topo.faces())
144 | _edges = []
145 | for edg in Topo(face).edges():
146 | _edges.append(edg)
147 | for edg in _edges:
148 | assert not edg.IsNull()
149 |
150 | def test_wires_out_of_scope(self):
151 | # check pointers going out of scope
152 | wire = next(self.topo.wires())
153 | _edges, _vertices = [], []
154 | for edg in WireExplorer(wire).ordered_edges():
155 | _edges.append(edg)
156 | for edg in _edges:
157 | assert not edg.IsNull()
158 | for vert in WireExplorer(wire).ordered_vertices():
159 | _vertices.append(vert)
160 | for v in _vertices:
161 | assert not v.IsNull()
162 |
163 |
164 | class TestEdge(unittest.TestCase):
165 | def test_creat_edge(self):
166 | # create a box
167 | b = get_test_box_shape()
168 | # take the first edge
169 | t = Topo(b)
170 | edge_0 = next(t.edges()) # it's a TopoDS_Edge
171 | assert not edge_0.IsNull()
172 | # then create an edge
173 | my_edge = Edge(edge_0)
174 | assert not my_edge.IsNull()
175 | assert my_edge.tolerance == 1e-06
176 | assert my_edge.type == 'line'
177 | assert my_edge.length() == 30.
178 |
179 |
180 | class TestFace(unittest.TestCase):
181 | def test_creat_face(self):
182 | # create a box
183 | my_face = Face(BRepPrimAPI_MakeSphere(1., 1.).Face())
184 | assert not my_face.IsNull()
185 | assert my_face.tolerance == 1e-06
186 | assert not my_face.is_planar()
187 | assert my_face.is_trimmed()
188 |
189 |
190 | class TestWire(unittest.TestCase):
191 | def test_creat_face(self):
192 | # create a box
193 | b = get_test_box_shape()
194 | # take the first edge
195 | t = Topo(b)
196 | wire = next(t.wires())
197 | my_wire = Wire(wire)
198 | assert not my_wire.IsNull()
199 | assert my_wire.tolerance == 1e-06
200 |
201 |
202 | class TestVertex(unittest.TestCase):
203 | def test_creat_vertex(self):
204 | my_vertex = Vertex(1., 2., -2.6)
205 | assert my_vertex.tolerance == 1e-06
206 | assert my_vertex.x == 1.
207 | assert my_vertex.y == 2.
208 | assert my_vertex.z == -2.6
209 |
210 |
211 | class TestShell(unittest.TestCase):
212 | def test_creat_shell(self):
213 | my_shell = Shell(BRepPrimAPI_MakeBox(10, 20, 30).Shell())
214 | assert my_shell.tolerance == 1e-06
215 |
216 |
217 | class TestSolid(unittest.TestCase):
218 | def test_creat_solid(self):
219 | my_solid = Solid(BRepPrimAPI_MakeBox(10, 20, 30).Solid())
220 | assert my_solid.tolerance == 1e-06
221 |
222 |
223 | def suite():
224 | test_suite = unittest.TestSuite()
225 | return test_suite
226 |
227 | if __name__ == "__main__":
228 | unittest.main()
229 |
--------------------------------------------------------------------------------