├── .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 | [![Build Status](https://travis-ci.org/tpaviot/pythonocc-utils.png?branch=master)](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 | --------------------------------------------------------------------------------