├── 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 ├── TopologyUtils.py ├── _config.yml ├── bottle.py ├── cadViewer.py ├── docs ├── images │ └── cadapp.png ├── index.html └── toDo_toFix.txt ├── dynamic3Dmod.py ├── icons ├── abcl.gif ├── acl.gif ├── arc3p.gif ├── arcc2p.gif ├── array.gif ├── cc3p.gif ├── cccirc.gif ├── ccirc.gif ├── cctan2.gif ├── cctan3.gif ├── circ.gif ├── cltan1.gif ├── cltan2.gif ├── del_c.gif ├── del_cel.gif ├── del_el.gif ├── del_g.gif ├── fillet.gif ├── hcl.gif ├── hvcl.gif ├── join.gif ├── lbcl.gif ├── line.gif ├── parcl.gif ├── perpcl.gif ├── poly.gif ├── rect.gif ├── refangcl.gif ├── rotate.gif ├── sep.gif ├── slot.gif ├── split.gif ├── stretch.gif ├── tpcl.gif ├── translate.gif └── vcl.gif ├── mainwindow.py ├── misc ├── bottle.py ├── bottle_updated.py ├── buildFaceBottomUp.py ├── circleexample.py ├── core_topology_local_ops.py ├── core_topology_traverse.py ├── example.py ├── example1.py ├── example_updated_to_run_on_v7.4.py ├── myqtDisplay.py └── tkrpncalc.py ├── myDisplay ├── OCCViewer.py ├── Readme.txt ├── SimpleGui.py ├── __init__.py ├── backend.py ├── icons │ ├── cursor-magnify-area.png │ ├── cursor-magnify.png │ ├── cursor-pan.png │ └── cursor-rotate.png ├── qtDisplay.py └── wxDisplay.py ├── rpnCalculator.py ├── sew.py ├── step ├── as1-oc-214.stp ├── as1_pe_203.stp └── tiltedCup.stp ├── stepXD.py ├── treelib ├── __init__.py ├── exceptions.py ├── node.py ├── plugins.py └── tree.py ├── treemodel.py ├── unusedDynamic.py ├── versioning.txt ├── waysToMoveShape.txt └── workplane.py /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 | def __init__(self, filename): 30 | if not os.path.isfile(filename): 31 | raise IOError("File %s not found.\n" % filename) 32 | self._filename = filename 33 | self._toScaleU = 1.0 34 | self._toScaleV = 1.0 35 | self._toRepeatU = 1.0 36 | self._toRepeatV = 1.0 37 | self._originU = 0.0 38 | self._originV = 0.0 39 | 40 | def TextureScale(self, toScaleU, toScaleV): 41 | self._toScaleU = toScaleU 42 | self._toScaleV = toScaleV 43 | 44 | def TextureRepeat(self, toRepeatU, toRepeatV): 45 | self._toRepeatU = toRepeatU 46 | self._toRepeatV = toRepeatV 47 | 48 | def TextureOrigin(self, originU, originV): 49 | self._originU = originU 50 | self._originV = originV 51 | 52 | def GetProperties(self): 53 | return (self._filename, 54 | self._toScaleU, self._toScaleV, 55 | self._toRepeatU, self._toRepeatV, 56 | self._originU, self._originV) 57 | -------------------------------------------------------------------------------- /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.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 | def __init__(self, wire): 32 | self.wire = wire 33 | self.edge_pairs = [] 34 | self.prev_edge = None 35 | self.we = WireExplorer(self.wire).ordered_edges() 36 | self.number_of_edges = self.we.__length_hint__() 37 | self.previous_edge = None 38 | self.current_edge = None 39 | self.first_edge = None 40 | self.index = 0 41 | 42 | def next(self): 43 | if self.index == 0: 44 | # first edge, need to set self.previous_edge 45 | self.previous_edge = next(self.we) 46 | self.current_edge = next(self.we) 47 | self.first_edge = self.previous_edge # for the last iteration 48 | self.index += 1 49 | return [self.previous_edge, self.current_edge] 50 | elif self.index == self.number_of_edges-1: 51 | # no next edge 52 | self.index += 1 53 | return [self.current_edge, self.first_edge] 54 | else: 55 | self.previous_edge = self.current_edge 56 | self.current_edge = next(self.we) 57 | self.index += 1 58 | return [self.previous_edge, self.current_edge] 59 | 60 | def __iter__(self): 61 | return self 62 | 63 | 64 | class LoopWirePairs(object): 65 | ''' 66 | for looping through consequtive wires 67 | assures that the returned edge pairs are ordered 68 | ''' 69 | def __init__(self, wireA, wireB): 70 | self.wireA = wireA 71 | self.wireB = wireB 72 | self.we_A = WireExplorer(self.wireA) 73 | self.we_B = WireExplorer(self.wireB) 74 | self.tp_A = Topo(self.wireA) 75 | self.tp_B = Topo(self.wireB) 76 | self.bt = BRep_Tool() 77 | self.vertsA = [v for v in self.we_A.ordered_vertices()] 78 | self.vertsB = [v for v in self.we_B.ordered_vertices()] 79 | 80 | self.edgesA = [v for v in WireExplorer(wireA).ordered_edges()] 81 | self.edgesB = [v for v in WireExplorer(wireB).ordered_edges()] 82 | 83 | self.pntsB = [self.bt.Pnt(v) for v in self.vertsB] 84 | self.number_of_vertices = len(self.vertsA) 85 | self.index = 0 86 | 87 | def closest_point(self, vertexFromWireA): 88 | pt = self.bt.Pnt(vertexFromWireA) 89 | distances = [pt.Distance(i) for i in self.pntsB] 90 | indx_max_dist = distances.index(min(distances)) 91 | return self.vertsB[indx_max_dist] 92 | 93 | def next(self): 94 | if self.index == self.number_of_vertices: 95 | raise StopIteration 96 | 97 | vert = self.vertsA[self.index] 98 | closest = self.closest_point(vert) 99 | edges_a = self.tp_A.edges_from_vertex(vert) 100 | edges_b = self.tp_B.edges_from_vertex(closest) 101 | a1, a2 = Edge(next(edges_a)), Edge(next(edges_a)) 102 | b1, b2 = Edge(next(edges_b)), Edge(next(edges_b)) 103 | mpA = a1.mid_point() 104 | self.index += 1 105 | 106 | if mpA.Distance(b1.mid_point()) < mpA.Distance(b2.mid_point()): 107 | return iter([a1, a2]), iter([b1, b2]) 108 | else: 109 | return iter([a1, a2]), iter([b2, b1]) 110 | 111 | def __iter__(self): 112 | return self 113 | -------------------------------------------------------------------------------- /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 (brepgprop_VolumeProperties, 42 | brepgprop_LinearProperties, 43 | brepgprop_SurfaceProperties) 44 | from OCC.Core.BRepCheck import (BRepCheck_Vertex, BRepCheck_Edge, BRepCheck_Wire, 45 | BRepCheck_Face, BRepCheck_Shell, BRepCheck_Analyzer) 46 | from OCC.GProp import GProp_GProps 47 | from OCC.Display.SimpleGui import init_display 48 | 49 | from OCCUtils.Common import get_boundingbox 50 | from OCCUtils.Construct import (make_vertex, TOLERANCE) 51 | from OCCUtils.types_lut import shape_lut, topo_lut, curve_lut, surface_lut 52 | 53 | #=========================================================================== 54 | # DISPLAY 55 | #=========================================================================== 56 | global display 57 | 58 | 59 | class singleton(object): 60 | def __init__(self, cls): 61 | self.cls = cls 62 | self.instance_container = [] 63 | 64 | def __call__(self, *args, **kwargs): 65 | if not len(self.instance_container): 66 | cls = functools.partial(self.cls, *args, **kwargs) 67 | self.instance_container.append(cls()) 68 | return self.instance_container[0] 69 | 70 | 71 | @singleton 72 | class Display(object): 73 | def __init__(self): 74 | self.display, self.start_display, self.add_menu, self.add_function_to_menu = init_display() 75 | 76 | def __call__(self, *args, **kwargs): 77 | return self.display.DisplayShape(*args, **kwargs) 78 | 79 | #============ 80 | # base class 81 | #============ 82 | 83 | 84 | class BaseObject(object): 85 | """base class for all objects""" 86 | def __init__(self, name=None, tolerance=TOLERANCE): 87 | self.GlobalProperties = GlobalProperties(self) 88 | self.name = name 89 | self._dirty = False 90 | self.tolerance = tolerance 91 | self.display_set = False 92 | 93 | @property 94 | def is_dirty(self): 95 | '''when an object is dirty, its topology will be 96 | rebuild when update is called''' 97 | return self._dirty 98 | 99 | @is_dirty.setter 100 | def is_dirty(self, _bool): 101 | self._dirty = bool(_bool) 102 | 103 | @property 104 | def topo_type(self): 105 | return topo_lut[self.ShapeType()] 106 | 107 | @property 108 | def geom_type(self): 109 | if self.topo_type == 'edge': 110 | return curve_lut[self.ShapeType()] 111 | if self.topo_type == 'face': 112 | return surface_lut[self.adaptor.GetType()] 113 | else: 114 | raise ValueError('geom_type works only for edges and faces...') 115 | 116 | def set_display(self, display): 117 | if hasattr(display, 'DisplayShape'): 118 | self.display_set = True 119 | self.display = display 120 | else: 121 | raise ValueError('not a display') 122 | 123 | def check(self): 124 | """ 125 | """ 126 | _check = dict(vertex=BRepCheck_Vertex, edge=BRepCheck_Edge, 127 | wire=BRepCheck_Wire, face=BRepCheck_Face, 128 | shell=BRepCheck_Shell) 129 | _check[self.topo_type] 130 | # TODO: BRepCheck will be able to inform *what* actually is the matter, 131 | # though implementing this still is a bit of work... 132 | raise NotImplementedError 133 | 134 | def is_valid(self): 135 | analyse = BRepCheck_Analyzer(self) 136 | ok = analyse.IsValid() 137 | if ok: 138 | return True 139 | else: 140 | return False 141 | 142 | def copy(self): 143 | """ 144 | 145 | :return: 146 | """ 147 | cp = BRepBuilderAPI_Copy(self) 148 | cp.Perform(self) 149 | # get the class, construct a new instance 150 | # cast the cp.Shape() to its specific TopoDS topology 151 | _copy = self.__class__(shape_lut(cp.Shape())) 152 | return _copy 153 | 154 | def distance(self, other): 155 | ''' 156 | return the minimum distance 157 | 158 | :return: minimum distance, 159 | minimum distance points on shp1 160 | minimum distance points on shp2 161 | ''' 162 | return minimum_distance(self, other) 163 | 164 | def show(self, *args, **kwargs): 165 | """ 166 | renders the topological entity in the viewer 167 | 168 | :param update: redraw the scene or not 169 | """ 170 | if not self.display_set: 171 | Display()(self, *args, **kwargs) 172 | else: 173 | self.disp.DisplayShape(*args, **kwargs) 174 | 175 | def build(self): 176 | if self.name.startswith('Vertex'): 177 | self = make_vertex(self) 178 | 179 | def __eq__(self, other): 180 | return self.IsEqual(other) 181 | 182 | def __ne__(self, other): 183 | return not self.__eq__(other) 184 | 185 | 186 | class GlobalProperties(object): 187 | ''' 188 | global properties for all topologies 189 | ''' 190 | def __init__(self, instance): 191 | self.instance = instance 192 | 193 | @property 194 | def system(self): 195 | self._system = GProp_GProps() 196 | # todo, type should be abstracted with TopoDS... 197 | _topo_type = self.instance.topo_type 198 | if _topo_type == 'face' or _topo_type == 'shell': 199 | brepgprop_SurfaceProperties(self.instance, self._system) 200 | elif _topo_type == 'edge': 201 | brepgprop_LinearProperties(self.instance, self._system) 202 | elif _topo_type == 'solid': 203 | brepgprop_VolumeProperties(self.instance, self._system) 204 | return self._system 205 | 206 | def centre(self): 207 | """ 208 | :return: centre of the entity 209 | """ 210 | return self.system.CentreOfMass() 211 | 212 | def inertia(self): 213 | '''returns the inertia matrix''' 214 | return self.system.MatrixOfInertia(), self.system.MomentOfInertia() 215 | 216 | def area(self): 217 | '''returns the area of the surface''' 218 | return self.system.Mass() 219 | 220 | def bbox(self): 221 | ''' 222 | returns the bounding box of the face 223 | ''' 224 | return get_boundingbox(self.instance) 225 | -------------------------------------------------------------------------------- /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.Geom import Geom_Curve 21 | from OCC.GeomAPI import GeomAPI_ProjectPointOnSurf 22 | from OCC.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.GeomLProp import GeomLProp_SLProps 27 | from OCC.Core.BRepTools import breptools_UVBounds 28 | from OCC.Core.BRepAdaptor import BRepAdaptor_Surface, BRepAdaptor_HSurface 29 | from OCC.ShapeAnalysis import ShapeAnalysis_Surface 30 | from OCC.GeomProjLib import geomprojlib 31 | from OCC.Adaptor3d import Adaptor3d_IsoCurve 32 | from OCC.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_handle, 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, (_domain[2] - _domain[3])/div 65 | 66 | if u in _domain: 67 | low, hi = u-_domain[0], u-_domain[1] 68 | if low < hi: 69 | u = u - delta_u 70 | else: 71 | u = u + delta_u 72 | 73 | if v in _domain: 74 | low, hi = v-_domain[2], v-_domain[3] 75 | if low < hi: 76 | v = v - delta_v 77 | else: 78 | v = v + delta_v 79 | 80 | self._curvature.SetParameters(u, v) 81 | self._curvature_initiated = True 82 | 83 | return self._curvature 84 | 85 | def gaussian_curvature(self, u, v): 86 | return self.curvature(u, v).GaussianCurvature() 87 | 88 | def min_curvature(self, u, v): 89 | return self.curvature(u, v).MinCurvature() 90 | 91 | def mean_curvature(self, u, v): 92 | return self.curvature(u, v).MeanCurvature() 93 | 94 | def max_curvature(self, u, v): 95 | return self.curvature(u, v).MaxCurvature() 96 | 97 | def normal(self, u, v): 98 | # TODO: should make this return a gp_Vec 99 | curv = self.curvature(u, v) 100 | if curv.IsNormalDefined(): 101 | return curv.Normal() 102 | else: 103 | raise ValueError('normal is not defined at u,v: {0}, {1}'.format(u, v)) 104 | 105 | def tangent(self, u, v): 106 | dU, dV = gp_Dir(), gp_Dir() 107 | curv = self.curvature(u, v) 108 | if curv.IsTangentUDefined() and curv.IsTangentVDefined(): 109 | curv.TangentU(dU), curv.TangentV(dV) 110 | return dU, dV 111 | else: 112 | return None, None 113 | 114 | def radius(self, u, v): 115 | '''returns the radius at u 116 | ''' 117 | # TODO: SHOULD WE RETURN A SIGNED RADIUS? ( get rid of abs() )? 118 | try: 119 | _crv_min = 1./self.min_curvature(u, v) 120 | except ZeroDivisionError: 121 | _crv_min = 0. 122 | 123 | try: 124 | _crv_max = 1./self.max_curvature(u, v) 125 | except ZeroDivisionError: 126 | _crv_max = 0. 127 | return abs((_crv_min+_crv_max)/2.) 128 | 129 | 130 | class Face(TopoDS_Face, BaseObject): 131 | """high level surface API 132 | object is a Face if part of a Solid 133 | otherwise the same methods do apply, apart from the topology obviously 134 | """ 135 | def __init__(self, face): 136 | ''' 137 | ''' 138 | assert isinstance(face, TopoDS_Face), 'need a TopoDS_Face, got a %s' % face.__class__ 139 | assert not face.IsNull() 140 | super(Face, self).__init__() 141 | BaseObject.__init__(self, 'face') 142 | # we need to copy the base shape using the following three 143 | # lines 144 | assert self.IsNull() 145 | self.TShape(face.TShape()) 146 | self.Location(face.Location()) 147 | self.Orientation(face.Orientation()) 148 | assert not self.IsNull() 149 | 150 | # cooperative classes 151 | self.DiffGeom = DiffGeomSurface(self) 152 | 153 | # STATE; whether cooperative classes are yet initialized 154 | self._curvature_initiated = False 155 | self._geometry_lookup_init = False 156 | 157 | #=================================================================== 158 | # properties 159 | #=================================================================== 160 | self._h_srf = None 161 | self._srf = None 162 | self._adaptor = None 163 | self._adaptor_handle = None 164 | self._classify_uv = None # cache the u,v classifier, no need to rebuild for every sample 165 | self._topo = None 166 | 167 | # aliasing of useful methods 168 | def is_u_periodic(self): 169 | return self.adaptor.IsUPeriodic() 170 | 171 | def is_v_periodic(self): 172 | return self.adaptor.IsVPeriodic() 173 | 174 | def is_u_closed(self): 175 | return self.adaptor.IsUClosed() 176 | 177 | def is_v_closed(self): 178 | return self.adaptor.IsVClosed() 179 | 180 | def is_u_rational(self): 181 | return self.adaptor.IsURational() 182 | 183 | def is_v_rational(self): 184 | return self.adaptor.IsVRational() 185 | 186 | def u_degree(self): 187 | return self.adaptor.UDegree() 188 | 189 | def v_degree(self): 190 | return self.adaptor.VDegree() 191 | 192 | def u_continuity(self): 193 | return self.adaptor.UContinuity() 194 | 195 | def v_continuity(self): 196 | return self.adaptor.VContinuity() 197 | 198 | def domain(self): 199 | '''the u,v domain of the curve 200 | :return: UMin, UMax, VMin, VMax 201 | ''' 202 | return breptools_UVBounds(self) 203 | 204 | def mid_point(self): 205 | """ 206 | :return: the parameter at the mid point of the face, 207 | and its corresponding gp_Pnt 208 | """ 209 | u_min, u_max, v_min, v_max = self.domain() 210 | u_mid = (u_min + u_max) / 2. 211 | v_mid = (v_min + v_max) / 2. 212 | return ((u_mid, v_mid), self.adaptor.Value(u_mid, v_mid)) 213 | 214 | @property 215 | def topo(self): 216 | if self._topo is not None: 217 | return self._topo 218 | else: 219 | self._topo = Topo(self) 220 | return self._topo 221 | 222 | @property 223 | def surface(self): 224 | if self._srf is None or self.is_dirty: 225 | self._h_srf = BRep_Tool_Surface(self) 226 | self._srf = self._h_srf.GetObject() 227 | return self._srf 228 | 229 | @property 230 | def surface_handle(self): 231 | if self._h_srf is None or self.is_dirty: 232 | self.surface # force building handle 233 | return self._h_srf 234 | 235 | @property 236 | def adaptor(self): 237 | if self._adaptor is not None and not self.is_dirty: 238 | pass 239 | else: 240 | self._adaptor = BRepAdaptor_Surface(self) 241 | self._adaptor_handle = BRepAdaptor_HSurface() 242 | self._adaptor_handle.Set(self._adaptor) 243 | return self._adaptor 244 | 245 | @property 246 | def adaptor_handle(self): 247 | if self._adaptor_handle is not None and not self.is_dirty: 248 | pass 249 | else: 250 | self.adaptor 251 | return self._adaptor_handle 252 | 253 | def is_closed(self): 254 | sa = ShapeAnalysis_Surface(self.surface_handle) 255 | # sa.GetBoxUF() 256 | return sa.IsUClosed(), sa.IsVClosed() 257 | 258 | def is_planar(self, tol=TOLERANCE): 259 | '''checks if the surface is planar within a tolerance 260 | :return: bool, gp_Pln 261 | ''' 262 | print(self.surface_handle) 263 | is_planar_surface = GeomLib_IsPlanarSurface(self.surface_handle, tol) 264 | return is_planar_surface.IsPlanar() 265 | 266 | def is_trimmed(self): 267 | """ 268 | :return: True if the Wire delimiting the Face lies on the bounds 269 | of the surface 270 | if this is not the case, the wire represents a contour that delimits 271 | the face [ think cookie cutter ] 272 | and implies that the surface is trimmed 273 | """ 274 | _round = lambda x: round(x, 3) 275 | a = map(_round, breptools_UVBounds(self)) 276 | b = map(_round, self.adaptor.Surface().Surface().GetObject().Bounds()) 277 | if a != b: 278 | print('a,b', a, b) 279 | return True 280 | return False 281 | 282 | def on_trimmed(self, u, v): 283 | '''tests whether the surface at the u,v parameter has been trimmed 284 | ''' 285 | if self._classify_uv is None: 286 | self._classify_uv = BRepTopAdaptor_FClass2d(self, 1e-9) 287 | uv = gp_Pnt2d(u, v) 288 | if self._classify_uv.Perform(uv) == TopAbs_IN: 289 | return True 290 | else: 291 | return False 292 | 293 | def parameter_to_point(self, u, v): 294 | '''returns the coordinate at u,v 295 | ''' 296 | return self.surface.Value(u, v) 297 | 298 | def point_to_parameter(self, pt): 299 | ''' 300 | returns the uv value of a point on a surface 301 | @param pt: 302 | ''' 303 | sas = ShapeAnalysis_Surface(self.surface_handle) 304 | uv = sas.ValueOfUV(pt, self.tolerance) 305 | return uv.Coord() 306 | 307 | def continuity_edge_face(self, edge, face): 308 | """ 309 | compute the continuity between two faces at :edge: 310 | 311 | :param edge: an Edge or TopoDS_Edge from :face: 312 | :param face: a Face or TopoDS_Face 313 | :return: bool, GeomAbs_Shape if it has continuity, otherwise 314 | False, None 315 | """ 316 | bt = BRep_Tool() 317 | if bt.HasContinuity(edge, self, face): 318 | continuity = bt.Continuity(edge, self, face) 319 | return True, continuity 320 | else: 321 | return False, None 322 | 323 | #=========================================================================== 324 | # Surface.project 325 | # project curve, point on face 326 | #=========================================================================== 327 | 328 | def project_vertex(self, pnt, tol=TOLERANCE): 329 | '''projects self with a point, curve, edge, face, solid 330 | method wraps dealing with the various topologies 331 | 332 | if other is a point: 333 | returns uv, point 334 | 335 | ''' 336 | if isinstance(pnt, TopoDS_Vertex): 337 | pnt = BRep_Tool.Pnt(pnt) 338 | 339 | proj = GeomAPI_ProjectPointOnSurf(pnt, self.surface_handle, tol) 340 | uv = proj.LowerDistanceParameters() 341 | proj_pnt = proj.NearestPoint() 342 | 343 | return uv, proj_pnt 344 | 345 | def project_curve(self, other): 346 | # this way Geom_Circle and alike are valid too 347 | if (isinstance(other, TopoDS_Edge) or 348 | isinstance(other, Geom_Curve) or 349 | issubclass(other, Geom_Curve)): 350 | # convert edge to curve 351 | first, last = topexp.FirstVertex(other), topexp.LastVertex(other) 352 | lbound, ubound = BRep_Tool().Parameter(first, other), BRep_Tool().Parameter(last, other) 353 | other = BRep_Tool.Curve(other, lbound, ubound).GetObject() 354 | return geomprojlib.Project(other, self.surface_handle) 355 | 356 | def project_edge(self, edg): 357 | if hasattr(edg, 'adaptor'): 358 | return self.project_curve(self, self.adaptor) 359 | return self.project_curve(self, to_adaptor_3d(edg)) 360 | 361 | def iso_curve(self, u_or_v, param): 362 | """ 363 | get the iso curve from a u,v + parameter 364 | :param u_or_v: 365 | :param param: 366 | :return: 367 | """ 368 | uv = 0 if u_or_v == 'u' else 1 369 | iso = Adaptor3d_IsoCurve(self.adaptor_handle.GetHandle(), uv, param) 370 | return iso 371 | 372 | def edges(self): 373 | return [Edge(i) for i in WireExplorer(next(self.topo.wires())).ordered_edges()] 374 | 375 | def __repr__(self): 376 | return self.name 377 | 378 | def __str__(self): 379 | return self.__repr__() 380 | 381 | if __name__ == "__main__": 382 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere 383 | sph = BRepPrimAPI_MakeSphere(1, 1).Face() 384 | fc = Face(sph) 385 | print(fc.is_trimmed()) 386 | print(fc.is_planar()) 387 | -------------------------------------------------------------------------------- /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.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), 'need a TopoDS_Shell, got a %s' % shell.__class__ 30 | assert not shell.IsNull() 31 | super(Shell, self).__init__() 32 | BaseObject.__init__(self, 'shell') 33 | # we need to copy the base shape using the following three 34 | # lines 35 | assert self.IsNull() 36 | self.TShape(shell.TShape()) 37 | self.Location(shell.Location()) 38 | self.Orientation(shell.Orientation()) 39 | assert not self.IsNull() 40 | 41 | self.GlobalProperties = GlobalProperties(self) 42 | self._n += 1 43 | 44 | def analyse(self): 45 | """ 46 | 47 | :return: 48 | """ 49 | ss = ShapeAnalysis_Shell(self) 50 | if ss.HasFreeEdges(): 51 | bad_edges = [e for e in Topo(ss.BadEdges()).edges()] 52 | return bad_edges 53 | 54 | def Faces(self): 55 | """ 56 | 57 | :return: 58 | """ 59 | return Topo(self, True).faces() 60 | 61 | def Wires(self): 62 | """ 63 | :return: 64 | """ 65 | return Topo(self, True).wires() 66 | 67 | def Edges(self): 68 | return Topo(self, True).edges() 69 | -------------------------------------------------------------------------------- /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), 'need a TopoDS_Solid, got a %s' % solid.__class__ 28 | assert not solid.IsNull() 29 | super(Solid, self).__init__() 30 | BaseObject.__init__(self, 'solid') 31 | # we need to copy the base shape using the following three 32 | # lines 33 | assert self.IsNull() 34 | self.TShape(solid.TShape()) 35 | self.Location(solid.Location()) 36 | self.Orientation(solid.Orientation()) 37 | assert not self.IsNull() 38 | 39 | self.GlobalProperties = GlobalProperties(self) 40 | 41 | def shells(self): 42 | return (Shell(sh) for sh in Topo(self)) 43 | -------------------------------------------------------------------------------- /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 Handle_Geom_Plane, Handle_Geom_CylindricalSurface 24 | 25 | 26 | class ShapeToTopology(object): 27 | ''' 28 | looks up the topology type and returns the corresponding topological entity 29 | ''' 30 | def __init__(self): 31 | self.topoTypes = {TopAbs_VERTEX: topods.Vertex, 32 | TopAbs_EDGE: topods.Edge, 33 | TopAbs_FACE: topods.Face, 34 | TopAbs_WIRE: topods.Wire, 35 | TopAbs_SHELL: topods.Shell, 36 | TopAbs_SOLID: topods.Solid, 37 | TopAbs_COMPOUND: topods.Compound, 38 | TopAbs_COMPSOLID: topods.CompSolid, 39 | } 40 | 41 | def __call__(self, shape): 42 | if isinstance(shape, TopoDS_Shape): 43 | return self.topoTypes[shape.ShapeType()](shape) 44 | else: 45 | raise AttributeError('shape has not method `ShapeType`') 46 | 47 | def __getitem__(self, item): 48 | return self(item) 49 | 50 | 51 | class EnumLookup(object): 52 | """ 53 | perform bi-directional lookup of Enums'... 54 | """ 55 | def __init__(self, li_in, li_out): 56 | self.d = {} 57 | for a, b in zip(li_in, li_out): 58 | self.d[a] = b 59 | self.d[b] = a 60 | 61 | def __getitem__(self, item): 62 | return self.d[item] 63 | 64 | 65 | _curve_typesA = (GeomAbs_Line, GeomAbs_Circle, GeomAbs_Ellipse, 66 | GeomAbs_Hyperbola, GeomAbs_Parabola, 67 | GeomAbs_BezierCurve, GeomAbs_BSplineCurve, GeomAbs_OtherCurve) 68 | _curve_typesB = ('line', 'circle', 'ellipse', 'hyperbola', 'parabola', 69 | 'bezier', 'spline', 'other') 70 | _surface_typesA = (GeomAbs_Plane, GeomAbs_Cylinder, GeomAbs_Cone, 71 | GeomAbs_Sphere, GeomAbs_Torus, GeomAbs_BezierSurface, 72 | GeomAbs_BSplineSurface, GeomAbs_SurfaceOfRevolution, 73 | GeomAbs_SurfaceOfExtrusion, 74 | GeomAbs_OffsetSurface, GeomAbs_OtherSurface) 75 | _surface_typesB = ('plane', 'cylinder', 'cone', 'sphere', 'torus', 'bezier', 76 | 'spline', 'revolution', 'extrusion', 'offset', 'other') 77 | 78 | 79 | _stateA = ('in', 'out', 'on', 'unknown') 80 | _stateB = (TopAbs_IN, TopAbs_OUT, TopAbs_ON, TopAbs_UNKNOWN) 81 | 82 | 83 | _orientA = ['TopAbs_FORWARD', 'TopAbs_REVERSED', 'TopAbs_INTERNAL', 84 | 'TopAbs_EXTERNAL'] 85 | _orientB = [TopAbs_FORWARD, TopAbs_REVERSED, TopAbs_INTERNAL, 86 | TopAbs_EXTERNAL] 87 | 88 | 89 | _topoTypesA = ['vertex', 'edge', 'wire', 'face', 'shell', 90 | 'solid', 'compsolid', 'compound', 'shape'] 91 | _topoTypesB = [TopAbs_VERTEX, TopAbs_EDGE, TopAbs_WIRE, TopAbs_FACE, 92 | TopAbs_SHELL, TopAbs_SOLID, 93 | TopAbs_COMPSOLID, TopAbs_COMPOUND, TopAbs_SHAPE] 94 | 95 | 96 | _geom_types_a = ['line', 'circle', 'ellipse', 'hyperbola', 'parabola', 97 | 'beziercurve', 'bsplinecurve', 'othercurve'] 98 | _geom_types_b = [GeomAbs_Line, GeomAbs_Circle, GeomAbs_Ellipse, 99 | GeomAbs_Hyperbola, GeomAbs_Parabola, GeomAbs_BezierCurve, 100 | GeomAbs_BSplineCurve, GeomAbs_OtherCurve] 101 | 102 | 103 | # TODO: make a function that generalizes this, there is absolutely 104 | # no need for 2 lists to define an EnumLookup 105 | 106 | def fix_formatting(_str): 107 | return [i.strip() for i in _str.split(',')] 108 | 109 | _brep_check_a = fix_formatting("NoError, InvalidPointOnCurve,\ 110 | InvalidPointOnCurveOnSurface, InvalidPointOnSurface,\ 111 | No3DCurve, Multiple3DCurve, Invalid3DCurve, NoCurveOnSurface,\ 112 | InvalidCurveOnSurface, InvalidCurveOnClosedSurface, InvalidSameRangeFlag,\ 113 | InvalidSameParameterFlag,\ 114 | InvalidDegeneratedFlag, FreeEdge, InvalidMultiConnexity, InvalidRange,\ 115 | EmptyWire, RedundantEdge, SelfIntersectingWire, NoSurface,\ 116 | InvalidWire, RedundantWire, IntersectingWires, InvalidImbricationOfWires,\ 117 | EmptyShell, RedundantFace, UnorientableShape, NotClosed,\ 118 | NotConnected, SubshapeNotInShape, BadOrientation, BadOrientationOfSubshape,\ 119 | InvalidToleranceValue, CheckFail") 120 | 121 | _brep_check_b = [BRepCheck_NoError, BRepCheck_InvalidPointOnCurve, 122 | BRepCheck_InvalidPointOnCurveOnSurface, 123 | BRepCheck_InvalidPointOnSurface, 124 | BRepCheck_No3DCurve, BRepCheck_Multiple3DCurve, 125 | BRepCheck_Invalid3DCurve, BRepCheck_NoCurveOnSurface, 126 | BRepCheck_InvalidCurveOnSurface, 127 | BRepCheck_InvalidCurveOnClosedSurface, 128 | BRepCheck_InvalidSameRangeFlag, 129 | BRepCheck_InvalidSameParameterFlag, 130 | BRepCheck_InvalidDegeneratedFlag, BRepCheck_FreeEdge, 131 | BRepCheck_InvalidMultiConnexity, BRepCheck_InvalidRange, 132 | BRepCheck_EmptyWire, BRepCheck_RedundantEdge, 133 | BRepCheck_SelfIntersectingWire, BRepCheck_NoSurface, 134 | BRepCheck_InvalidWire, BRepCheck_RedundantWire, 135 | BRepCheck_IntersectingWires, 136 | BRepCheck_InvalidImbricationOfWires, 137 | BRepCheck_EmptyShell, BRepCheck_RedundantFace, 138 | BRepCheck_UnorientableShape, BRepCheck_NotClosed, 139 | BRepCheck_NotConnected, BRepCheck_SubshapeNotInShape, 140 | BRepCheck_BadOrientation, BRepCheck_BadOrientationOfSubshape, 141 | BRepCheck_InvalidToleranceValue, BRepCheck_CheckFail] 142 | 143 | brepcheck_lut = EnumLookup(_brep_check_a, _brep_check_b) 144 | curve_lut = EnumLookup(_curve_typesA, _curve_typesB) 145 | surface_lut = EnumLookup(_surface_typesA, _surface_typesB) 146 | state_lut = EnumLookup(_stateA, _stateB) 147 | orient_lut = EnumLookup(_orientA, _orientB) 148 | topo_lut = EnumLookup(_topoTypesA, _topoTypesB) 149 | shape_lut = ShapeToTopology() 150 | geom_lut = EnumLookup(_geom_types_a, _geom_types_b) 151 | 152 | # todo: refactor, these classes have been moved from the "Topology" directory 153 | # which had too many overlapping methods & classes, that are 154 | # now part of the KBE module... 155 | # still need to think what to do with these... 156 | # what_is_face should surely become a lut [ geom_lut? ] 157 | # i'm not sure whether casting to a gp_* is useful... 158 | 159 | classes = dir() 160 | geom_classes = [] 161 | for elem in classes: 162 | if elem.startswith('Geom') and not 'swig' in elem: 163 | geom_classes.append(elem) 164 | 165 | 166 | def what_is_face(face): 167 | ''' Returns all class names for which this class can be downcasted 168 | ''' 169 | if not face.ShapeType() == TopAbs_FACE: 170 | print('%s is not a TopAbs_FACE. Conversion impossible') 171 | return None 172 | hs = BRep_Tool_Surface(face) 173 | obj = hs.GetObject() 174 | result = [] 175 | for elem in classes: 176 | if (elem.startswith('Geom') and not 'swig' in elem): 177 | geom_classes.append(elem) 178 | # Run the test for each class 179 | for geom_class in geom_classes: 180 | if obj.IsKind(geom_class) and not geom_class in result: 181 | result.append(geom_class) 182 | return result 183 | 184 | 185 | def face_is_plane(face): 186 | ''' Returns True if the TopoDS_Shape is a plane, False otherwise 187 | ''' 188 | hs = BRep_Tool_Surface(face) 189 | downcast_result = Handle_Geom_Plane().DownCast(hs) 190 | # the handle is null if downcast failed or is not possible, 191 | # that is to say the face is not a plane 192 | if downcast_result.IsNull(): 193 | return False 194 | else: 195 | return True 196 | 197 | 198 | def shape_is_cylinder(face): 199 | ''' Returns True is the TopoDS_Shape is a cylinder, False otherwise 200 | ''' 201 | hs = BRep_Tool_Surface(face) 202 | downcast_result = Handle_Geom_CylindricalSurface().DownCast(hs) 203 | if downcast_result.IsNull(): 204 | return False 205 | else: 206 | return True 207 | -------------------------------------------------------------------------------- /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.gp import gp_Pnt, gp_Vec, gp_Dir, gp_XYZ, gp_Pnt2d 19 | from OCC.Core.TopoDS import TopoDS_Vertex 20 | from OCC.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 | _n = 0 31 | 32 | def __init__(self, x, y, z): 33 | super(Vertex, self).__init__() 34 | """Constructor for KbeVertex""" 35 | BaseObject.__init__(self, name='Vertex #{0}'.format(self._n)) 36 | 37 | self._n += 1 # should be a property of KbeObject 38 | self._pnt = gp_Pnt(x, y, z) 39 | self._vertex = make_vertex(self._pnt) 40 | TopoDS_Vertex.__init__(self, self._vertex) 41 | 42 | def _update(self): 43 | """ 44 | 45 | """ 46 | # TODO: perhaps should take an argument until which topological level 47 | # topological entities bound to the vertex should be updated too... 48 | reshape = ShapeBuild_ReShape() 49 | reshape.Replace(self._vertex, make_vertex(self._pnt)) 50 | 51 | @staticmethod 52 | def from_pnt(cls, pnt): 53 | x, y, z = pnt.X(), pnt.Y(), pnt.Z() 54 | return cls(x, y, z) 55 | 56 | @property 57 | def x(self): 58 | return self._pnt.X() 59 | 60 | @x.setter 61 | def x(self, val): 62 | self._pnt.SetX(val) 63 | self._update() 64 | 65 | @property 66 | def y(self): 67 | return self._pnt.Y() 68 | 69 | @y.setter 70 | def y(self, val): 71 | self._pnt.SetY(val) 72 | self._update() 73 | 74 | @property 75 | def z(self): 76 | return self._pnt.Z() 77 | 78 | @z.setter 79 | def z(self, val): 80 | self._pnt.SetZ(val) 81 | self._update() 82 | 83 | @property 84 | def xyz(self): 85 | return self._pnt.Coord() 86 | 87 | @xyz.setter 88 | def xyz(self, *val): 89 | self._pnt.SetXYZ(*val) 90 | self._update() 91 | 92 | def __repr__(self): 93 | return self.name 94 | 95 | @property 96 | def as_vec(self): 97 | '''returns a gp_Vec version of self''' 98 | return gp_Vec(*self._pnt.Coord()) 99 | 100 | @property 101 | def as_dir(self): 102 | '''returns a gp_Dir version of self''' 103 | return gp_Dir(*self._pnt.Coord()) 104 | 105 | @property 106 | def as_xyz(self): 107 | '''returns a gp_XYZ version of self''' 108 | return gp_XYZ(*self._pnt.Coord()) 109 | 110 | @property 111 | def as_pnt(self): 112 | return self._pnt 113 | 114 | @property 115 | def as_2d(self): 116 | '''returns a gp_Pnt2d version of self''' 117 | return gp_Pnt2d(*self._pnt.Coord()[:2]) 118 | -------------------------------------------------------------------------------- /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 | ''' 29 | assert isinstance(wire, TopoDS_Wire), 'need a TopoDS_Wire, got a %s' % wire.__class__ 30 | assert not wire.IsNull() 31 | super(Wire, self).__init__() 32 | BaseObject.__init__(self, 'wire') 33 | # we need to copy the base shape using the following three 34 | # lines 35 | assert self.IsNull() 36 | self.TShape(wire.TShape()) 37 | self.Location(wire.Location()) 38 | self.Orientation(wire.Orientation()) 39 | assert not self.IsNull() 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cadviewer 2 | Simple 3D CAD app using PythonOCC and PyQt5 3 | 4 | Jan 19, 2020: I think the code is now working pretty much as it was when I 5 | stopped maintaining it back in 2016. I jotted a brief summary and posted a 6 | current screenshot at https://dblanding.github.io/cadviewer/ 7 | 8 | Jan 17, 2020: Got construction lines (on the toolbar) working. 9 | Also got fillet and shell working. Measure pt_to_pt distance and edge_length 10 | (on the calculator) are now working. 11 | 12 | Jan 16, 2020: Updated 'bottle demo' and added it to the menu bar, 13 | enabling step by step building of the OCC Classic Bottle. 14 | 15 | Jan 4, 2020: Progress has been better than I had hoped. 16 | The basic GUI is all there with all the widgets, 17 | STEP files can be loaded and they show up both in the display 18 | and with their correct assembly structure in the assembly/parts tree, 19 | The RMB context menu works, 20 | Workplanes can be created using three different methods, 21 | The calculator works and seems to be communicating with the main window. 22 | 23 | 24 | This repo is like an old attic in a sense. It contains various code that I have 25 | written as I have experimented with what and how I might go about writing 26 | a CAD application built on PythonOCC. I decided to post it on GitHub. 27 | 28 | I stumbled across some work I did a few years ago, where I started to build a simple 29 | CAD app using PythonOCC running on Python 2.7 using PyQt4. Having not looked at it in 30 | over 3 years, I wasn't sure it would be worth the trouble to get it working again with 31 | PyhonOCC version 7.4.0 while switching to Python 3 and PyQt5 all at once. 32 | A screenshot from some old code posted online: 33 | https://sites.google.com/site/pythonocc/cadviewer 34 | reminds me that I was using PythonOCC version 0.16.3-dev at that time. 35 | With the recent release of PyOCC version7.4.0-beta, I decided to give it a go. 36 | I asked Thomas Paviot for useful resources to help me understand the changes 37 | in the API from version 0.16 to the current version. His advice was very helpful: 38 | 39 | """ 40 | pythonocc-0.16.3 is 4 years old, in the meantime code has changed because of : 41 | 42 | * API changes from opencascade. Have a look at the release notes for each release. Most changes occurred when switching to occt7x series (see https://www.opencascade.com/content/previous-releases for an history of opencascascade releases) ; 43 | 44 | * changes in pythonocc itself. There have been two major changes that you have to know about while porting your old code to the new release : 45 | 46 | 1. The package structure has changed. You have to move all 'from OCC.xxx import xxx' to 'from OCC.Core.xxx import xxx'. That's not a big deal. 47 | 48 | 2. There is not Handle anymore. GetHandle and GetObject methods have disappeared. Just pass the object itself, the wrapper decides wether it has to pass the Handle or the Object to the C++ layer. You can check this commit (https://github.com/tpaviot/pythonocc-demos/commit/e59acdce5720d84ce76134789b48c268e36446d6#diff-68b70730ce65eb74e098809766ab3d0d), where we ported the old 'occ bottle example'. 49 | """ 50 | 51 | 52 | Here's my todo list, roughly in order of priority: 53 | 54 | Get Geometry lines (on toolbar) working to make wire profiles. 55 | 56 | Adopt OCAF doc as tree model. Be able to save & load from file 57 | 58 | Clean up code with a linter. 59 | 60 | Assign a version number. 61 | 62 | Add ability to write STEP file of assembly selected from tree. 63 | 64 | Add ability to create, modify and move 3D parts & assemblies. 65 | checkout links in Jelle's old post: 66 | https://groups.google.com/forum/#!topic/pythonocc/Ed86PGoNtIs 67 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bottle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Adapted from 'Classic OCC Bootle Demo' 4 | # 5 | # This file is part of cadViewer. 6 | # The latest version of this file can be found at: 7 | # //https://github.com/dblanding/cadviewer 8 | # 9 | # cadViewer is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # cadViewer is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # if not, write to the Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | import math 25 | from OCC.Core.gp import (gp_Pnt, gp_OX, gp_Vec, gp_Trsf, gp_DZ, gp_Ax2, gp_Ax3, 26 | gp_Pnt2d, gp_Dir2d, gp_Ax2d) 27 | from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeSegment 28 | from OCC.Core.GCE2d import GCE2d_MakeSegment 29 | from OCC.Core.Geom import Geom_CylindricalSurface 30 | from OCC.Core.Geom2d import Geom2d_Ellipse, Geom2d_TrimmedCurve 31 | from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire, 32 | BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform, 33 | BRepBuilderAPI_MakeVertex) 34 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakePrism, BRepPrimAPI_MakeCylinder 35 | from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet 36 | from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse 37 | from OCC.Core.BRepOffsetAPI import (BRepOffsetAPI_MakeThickSolid, 38 | BRepOffsetAPI_ThruSections) 39 | from OCC.Core.BRepLib import breplib 40 | from OCC.Core.BRep import BRep_Builder 41 | from OCC.Core.GeomAbs import GeomAbs_Plane 42 | from OCC.Core.BRepAdaptor import BRepAdaptor_Surface 43 | from OCC.Core.TopoDS import topods, topods_Wire, TopoDS_Compound 44 | from OCC.Core.TopAbs import TopAbs_EDGE, TopAbs_FACE 45 | from OCC.Core.TopExp import TopExp_Explorer 46 | from OCC.Core.TopTools import TopTools_ListOfShape 47 | 48 | ##################### 49 | # # 50 | # Bottle Demo: # 51 | # # 52 | ##################### 53 | 54 | def face_is_plane(face): 55 | """ 56 | Returns True if the TopoDS_Shape is a plane, False otherwise 57 | """ 58 | surf = BRepAdaptor_Surface(face, True) 59 | surf_type = surf.GetType() 60 | return surf_type == GeomAbs_Plane 61 | 62 | def geom_plane_from_face(aFace): 63 | """ 64 | Returns the geometric plane entity from a planar surface 65 | """ 66 | return BRepAdaptor_Surface(aFace, True).Plane() 67 | 68 | # Bottle Dimensions... 69 | width = 50 70 | height = 70 71 | thickness = 30 72 | 73 | def makeBottle(): # complete bottle 74 | startBottle(complete=True) 75 | 76 | def startBottle(complete=False): 77 | """Build the classic OCC Bottle. 78 | 79 | complete=False: minus the neck fillet, shelling & threads. 80 | complete=True: including shelling & threads.""" 81 | 82 | partName = "Bottle-start" 83 | # The points we'll use to create the profile of the bottle's body 84 | aPnt1 = gp_Pnt(-width / 2.0, 0, 0) 85 | aPnt2 = gp_Pnt(-width / 2.0, -thickness / 4.0, 0) 86 | aPnt3 = gp_Pnt(0, -thickness / 2.0, 0) 87 | aPnt4 = gp_Pnt(width / 2.0, -thickness / 4.0, 0) 88 | aPnt5 = gp_Pnt(width / 2.0, 0, 0) 89 | 90 | aArcOfCircle = GC_MakeArcOfCircle(aPnt2, aPnt3, aPnt4) 91 | aSegment1 = GC_MakeSegment(aPnt1, aPnt2) 92 | aSegment2 = GC_MakeSegment(aPnt4, aPnt5) 93 | 94 | # Could also construct the line edges directly using the points instead of the resulting line 95 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value()) 96 | aEdge2 = BRepBuilderAPI_MakeEdge(aArcOfCircle.Value()) 97 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment2.Value()) 98 | 99 | # Create a wire out of the edges 100 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), aEdge2.Edge(), aEdge3.Edge()) 101 | 102 | # Quick way to specify the X axis 103 | xAxis = gp_OX() 104 | 105 | # Set up the mirror 106 | aTrsf = gp_Trsf() 107 | aTrsf.SetMirror(xAxis) 108 | 109 | # Apply the mirror transformation 110 | aBRespTrsf = BRepBuilderAPI_Transform(aWire.Wire(), aTrsf) 111 | 112 | # Get the mirrored shape back out of the transformation and convert back to a wire 113 | aMirroredShape = aBRespTrsf.Shape() 114 | 115 | # A wire instead of a generic shape now 116 | aMirroredWire = topods.Wire(aMirroredShape) 117 | 118 | # Combine the two constituent wires 119 | mkWire = BRepBuilderAPI_MakeWire() 120 | mkWire.Add(aWire.Wire()) 121 | mkWire.Add(aMirroredWire) 122 | myWireProfile = mkWire.Wire() 123 | 124 | # The face that we'll sweep to make the prism 125 | myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile) 126 | 127 | # We want to sweep the face along the Z axis to the height 128 | aPrismVec = gp_Vec(0, 0, height) 129 | myBody = BRepPrimAPI_MakePrism(myFaceProfile.Face(), aPrismVec) 130 | 131 | # Add fillets to all edges through the explorer 132 | mkFillet = BRepFilletAPI_MakeFillet(myBody.Shape()) 133 | anEdgeExplorer = TopExp_Explorer(myBody.Shape(), TopAbs_EDGE) 134 | 135 | while anEdgeExplorer.More(): 136 | anEdge = topods.Edge(anEdgeExplorer.Current()) 137 | mkFillet.Add(thickness / 12.0, anEdge) 138 | 139 | anEdgeExplorer.Next() 140 | 141 | myBody = mkFillet.Shape() 142 | 143 | # Create the neck of the bottle 144 | neckLocation = gp_Pnt(0, 0, height) 145 | neckAxis = gp_DZ() 146 | neckAx2 = gp_Ax2(neckLocation, neckAxis) 147 | 148 | myNeckRadius = thickness / 4.0 149 | myNeckHeight = height / 10.0 150 | 151 | mkCylinder = BRepPrimAPI_MakeCylinder(neckAx2, myNeckRadius, myNeckHeight) 152 | myBody = BRepAlgoAPI_Fuse(myBody, mkCylinder.Shape()) 153 | if not complete: # quit here 154 | #uid = win.getNewPartUID(myBody.Shape(), name=partName) 155 | #win.redraw() 156 | return 157 | 158 | partName = "Bottle-complete" 159 | # Our goal is to find the highest Z face and remove it 160 | faceToRemove = None 161 | zMax = -1 162 | 163 | # We have to work our way through all the faces to find the highest Z face 164 | aFaceExplorer = TopExp_Explorer(myBody.Shape(), TopAbs_FACE) 165 | while aFaceExplorer.More(): 166 | aFace = topods.Face(aFaceExplorer.Current()) 167 | 168 | if face_is_plane(aFace): 169 | aPlane = geom_plane_from_face(aFace) 170 | 171 | # We want the highest Z face, so compare this to the previous faces 172 | aPnt = aPlane.Location() 173 | aZ = aPnt.Z() 174 | if aZ > zMax: 175 | zMax = aZ 176 | faceToRemove = aFace 177 | 178 | aFaceExplorer.Next() 179 | 180 | facesToRemove = TopTools_ListOfShape() 181 | facesToRemove.Append(faceToRemove) 182 | 183 | myBody = BRepOffsetAPI_MakeThickSolid(myBody.Shape(), facesToRemove, -thickness / 50.0, 0.001) 184 | 185 | # Set up our surfaces for the threading on the neck 186 | neckAx2_Ax3 = gp_Ax3(neckLocation, gp_DZ()) 187 | aCyl1 = Geom_CylindricalSurface(neckAx2_Ax3, myNeckRadius * 0.99) 188 | aCyl2 = Geom_CylindricalSurface(neckAx2_Ax3, myNeckRadius * 1.05) 189 | 190 | # Set up the curves for the threads on the bottle's neck 191 | aPnt = gp_Pnt2d(2.0 * math.pi, myNeckHeight / 2.0) 192 | aDir = gp_Dir2d(2.0 * math.pi, myNeckHeight / 4.0) 193 | anAx2d = gp_Ax2d(aPnt, aDir) 194 | 195 | aMajor = 2.0 * math.pi 196 | aMinor = myNeckHeight / 10.0 197 | 198 | anEllipse1 = Geom2d_Ellipse(anAx2d, aMajor, aMinor) 199 | anEllipse2 = Geom2d_Ellipse(anAx2d, aMajor, aMinor / 4.0) 200 | 201 | anArc1 = Geom2d_TrimmedCurve(anEllipse1, 0, math.pi) 202 | anArc2 = Geom2d_TrimmedCurve(anEllipse2, 0, math.pi) 203 | 204 | anEllipsePnt1 = anEllipse1.Value(0) 205 | anEllipsePnt2 = anEllipse1.Value(math.pi) 206 | 207 | aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2) 208 | 209 | # Build edges and wires for threading 210 | anEdge1OnSurf1 = BRepBuilderAPI_MakeEdge(anArc1, aCyl1) 211 | anEdge2OnSurf1 = BRepBuilderAPI_MakeEdge(aSegment.Value(), aCyl1) 212 | anEdge1OnSurf2 = BRepBuilderAPI_MakeEdge(anArc2, aCyl2) 213 | anEdge2OnSurf2 = BRepBuilderAPI_MakeEdge(aSegment.Value(), aCyl2) 214 | 215 | threadingWire1 = BRepBuilderAPI_MakeWire(anEdge1OnSurf1.Edge(), anEdge2OnSurf1.Edge()) 216 | threadingWire2 = BRepBuilderAPI_MakeWire(anEdge1OnSurf2.Edge(), anEdge2OnSurf2.Edge()) 217 | 218 | # Compute the 3D representations of the edges/wires 219 | breplib.BuildCurves3d(threadingWire1.Shape()) 220 | breplib.BuildCurves3d(threadingWire2.Shape()) 221 | 222 | # Create the surfaces of the threading 223 | aTool = BRepOffsetAPI_ThruSections(True) 224 | aTool.AddWire(threadingWire1.Wire()) 225 | aTool.AddWire(threadingWire2.Wire()) 226 | aTool.CheckCompatibility(False) 227 | myThreading = aTool.Shape() 228 | 229 | # Build the resulting compound 230 | aRes = TopoDS_Compound() 231 | aBuilder = BRep_Builder() 232 | aBuilder.MakeCompound(aRes) 233 | aBuilder.Add(aRes, myBody.Shape()) 234 | aBuilder.Add(aRes, myThreading) 235 | #uid = win.getNewPartUID(aRes, name=partName) 236 | #win.redraw() 237 | 238 | # Make Bottle step by step... 239 | def makePoints(): 240 | global aPnt1, aPnt2, aPnt3, aPnt4, aPnt5 241 | aPnt1 = gp_Pnt(-width / 2., 0, 0) 242 | aPnt2 = gp_Pnt(-width / 2., -thickness / 4., 0) 243 | aPnt3 = gp_Pnt(0, -thickness / 2., 0) 244 | aPnt4 = gp_Pnt(width / 2., -thickness / 4., 0) 245 | aPnt5 = gp_Pnt(width / 2., 0, 0) 246 | # points aren't visible on screen 247 | # make vertices in order to see them 248 | V1 = BRepBuilderAPI_MakeVertex(aPnt1) 249 | V2 = BRepBuilderAPI_MakeVertex(aPnt2) 250 | V3 = BRepBuilderAPI_MakeVertex(aPnt3) 251 | V4 = BRepBuilderAPI_MakeVertex(aPnt4) 252 | V5 = BRepBuilderAPI_MakeVertex(aPnt5) 253 | # add dummy vertex above bottle just to set view size 254 | V6 = BRepBuilderAPI_MakeVertex(gp_Pnt(0, 0, height * 1.1)) 255 | return (V1, V2, V3, V4, V5, V6) 256 | 257 | def makeLines(): 258 | global aEdge1, aEdge2, aEdge3 259 | # Make type 'Geom_TrimmedCurve' from type 'gp_Pnt' 260 | aArcOfCircle = GC_MakeArcOfCircle(aPnt2, aPnt3, aPnt4) 261 | aSegment1 = GC_MakeSegment(aPnt1, aPnt2) 262 | aSegment2 = GC_MakeSegment(aPnt4, aPnt5) 263 | # Make type 'TopoDS_Edge' from type 'Geom_TrimmedCurve' 264 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value()) 265 | aEdge2 = BRepBuilderAPI_MakeEdge(aArcOfCircle.Value()) 266 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment2.Value()) 267 | return (aEdge1.Edge(), aEdge2.Edge(), aEdge3.Edge()) 268 | 269 | def makeHalfWire(): 270 | global aWire 271 | # Make type 'TopoDS_Wire' from type 'TopoDS_Edge' 272 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 273 | aEdge2.Edge(), 274 | aEdge3.Edge()).Wire() 275 | return aWire 276 | 277 | def makeWholeWire(): 278 | global myWireProfile 279 | xAxis = gp_OX() 280 | # Set up the mirror 281 | aTrsf = gp_Trsf() 282 | aTrsf.SetMirror(xAxis) 283 | # Apply the mirror transform 284 | aBRepTrsf = BRepBuilderAPI_Transform(aWire, aTrsf) 285 | # Convert mirrored shape to a wire 286 | aMirroredShape = aBRepTrsf.Shape() 287 | aMirroredWire = topods_Wire(aMirroredShape) 288 | # Combine the two wires 289 | mkWire = BRepBuilderAPI_MakeWire() 290 | mkWire.Add(aWire) 291 | mkWire.Add(aMirroredWire) 292 | myWireProfile = mkWire.Wire() 293 | return myWireProfile 294 | 295 | def makeFace(): 296 | global myFaceProfile 297 | myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile) 298 | if myFaceProfile.IsDone(): 299 | bottomFace = myFaceProfile.Face() 300 | return bottomFace 301 | 302 | def makeBody(): 303 | aPrismVec = gp_Vec(0, 0, height) 304 | myBody = BRepPrimAPI_MakePrism(myFaceProfile.Shape(), 305 | aPrismVec).Shape() 306 | return myBody 307 | 308 | def addNeck(): 309 | neckLocation = gp_Pnt(0, 0, height) 310 | neckNormal = gp_DZ() 311 | neckAx2 = gp_Ax2(neckLocation, neckNormal) 312 | myNeckRadius = thickness / 4. 313 | myNeckHeight = height / 10. 314 | MKCylinder = BRepPrimAPI_MakeCylinder(neckAx2, 315 | myNeckRadius, 316 | myNeckHeight) 317 | myNeck = MKCylinder.Shape() 318 | return myNeck 319 | -------------------------------------------------------------------------------- /docs/images/cadapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/docs/images/cadapp.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADViewer: A Simple CAD App written in Python 6 | 7 | 8 |

9 |

CADViewer: A Simple CAD App written in Python 3, built on PythonOCC & PyQt5

10 |

1/20/2020

11 |

I just finished updating an old 3D CAD project to run on the latest version of PythonOCC (version '7.4.0-rc1'), with Python 3.7 and PyQt5. The name 'CADViewer' for the old project seemed appropriate mostly because that was the limit of its functionality. You could open a STEP file and display the 3D model and display its assembly structure, but that's about it. The code hadn't been touched since September, 2016. It ran on PythonOCC version '0.16.3' with Python 2.7 and PyQt4. I ended up needing to tweak a lot of things in order to get it running again, but here it is, (functionally) as it was 3+ years ago.

12 |

My goals in this project have remained roughly consistent. I'm scratching an itch to build a 3D CAD App that provides useful functionality and is also fun to use. In my work as a mechanical design engineer, my favorite CAD was SolidDesigner (3D) and me10 (2D), originally developed by HP, then spun off as CoCreate (and later purchased by PTC). I have borrowed rather heavily from my experience with SolidDesigner and have tried to mimic its functionality.

13 |

Enjoy!

14 |

Here is a brief summary of some of CADViewer's functionality:

15 |
    16 |
  • Able to load STEP files and display the 3D model and its associated assembly structure, complete with part and assembly names.
  • 17 |
  • Able to save the active part to a step file.
  • 18 |
  • The basic paradigm for creating or modifying 3D objects is to first make a 2D sketch using Construction lines and Geometry lines on the active workplane. The construction lines facilitate creation of an accurate 'layout' onto which geometry lines are then added. The geometry lines get converted to wire profiles which are then used to create or modify 3D shapes.
  • 19 |
  • There are no layers! They're not needed.
  • 20 |
  • Filleting and shelling of parts.
  • 21 |
  • Step-by-step construction of the classic OCC bottle is included.
  • 22 |
23 |

Project status:

24 |

My goal is to continue adding more functionality while maintaining the code up to date with the latest relaease of PythonOCC. Here are some of my most immediate things toDo/toFix .

25 |

Screenshot:

26 |

screenshot

27 |

Author and Maintainer:

28 |

Doug Blanding (dblanding@gmail.com)

29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/toDo_toFix.txt: -------------------------------------------------------------------------------- 1 | To Do: (new features or functions) 2 | ---------------------------------- 3 | Use OCAF format to represent Assy/Part model data. 4 | Save as an OCC.Core.TDocStd.TDocStd_Document document. 5 | Save any assembly in step format. 6 | Save Assy/Part model (TStd_Doc document) to file. 7 | Keep Assy/Part model in sync with Assy/Part view. 8 | 9 | Modifiable parameters using treeWidgetItems: 10 | - Draw / Hide 11 | - Change Name 12 | - Change Owner 13 | - Change Color 14 | 15 | Project face or edge onto active WP 16 | Delete 2D 17 | Delete 3D 18 | View by current WP & Fit 19 | RMB pop-up over graphics window: 20 | - Add indicator of current select mode 21 | 22 | 23 | To Fix: (stuff that's there but not working) 24 | ---------------------------------- 25 | Get 2D Geom working (see: core_geometry_curves_2d_from_curve.py, OCC bottle) 26 | snapping to intersection points on WP seems flaky 27 | FitAll zooms out to infinity for WP's 28 | -------------------------------------------------------------------------------- /icons/abcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/abcl.gif -------------------------------------------------------------------------------- /icons/acl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/acl.gif -------------------------------------------------------------------------------- /icons/arc3p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/arc3p.gif -------------------------------------------------------------------------------- /icons/arcc2p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/arcc2p.gif -------------------------------------------------------------------------------- /icons/array.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/array.gif -------------------------------------------------------------------------------- /icons/cc3p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cc3p.gif -------------------------------------------------------------------------------- /icons/cccirc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cccirc.gif -------------------------------------------------------------------------------- /icons/ccirc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/ccirc.gif -------------------------------------------------------------------------------- /icons/cctan2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cctan2.gif -------------------------------------------------------------------------------- /icons/cctan3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cctan3.gif -------------------------------------------------------------------------------- /icons/circ.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/circ.gif -------------------------------------------------------------------------------- /icons/cltan1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cltan1.gif -------------------------------------------------------------------------------- /icons/cltan2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/cltan2.gif -------------------------------------------------------------------------------- /icons/del_c.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/del_c.gif -------------------------------------------------------------------------------- /icons/del_cel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/del_cel.gif -------------------------------------------------------------------------------- /icons/del_el.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/del_el.gif -------------------------------------------------------------------------------- /icons/del_g.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/del_g.gif -------------------------------------------------------------------------------- /icons/fillet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/fillet.gif -------------------------------------------------------------------------------- /icons/hcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/hcl.gif -------------------------------------------------------------------------------- /icons/hvcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/hvcl.gif -------------------------------------------------------------------------------- /icons/join.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/join.gif -------------------------------------------------------------------------------- /icons/lbcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/lbcl.gif -------------------------------------------------------------------------------- /icons/line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/line.gif -------------------------------------------------------------------------------- /icons/parcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/parcl.gif -------------------------------------------------------------------------------- /icons/perpcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/perpcl.gif -------------------------------------------------------------------------------- /icons/poly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/poly.gif -------------------------------------------------------------------------------- /icons/rect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/rect.gif -------------------------------------------------------------------------------- /icons/refangcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/refangcl.gif -------------------------------------------------------------------------------- /icons/rotate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/rotate.gif -------------------------------------------------------------------------------- /icons/sep.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/sep.gif -------------------------------------------------------------------------------- /icons/slot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/slot.gif -------------------------------------------------------------------------------- /icons/split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/split.gif -------------------------------------------------------------------------------- /icons/stretch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/stretch.gif -------------------------------------------------------------------------------- /icons/tpcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/tpcl.gif -------------------------------------------------------------------------------- /icons/translate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/translate.gif -------------------------------------------------------------------------------- /icons/vcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/icons/vcl.gif -------------------------------------------------------------------------------- /misc/buildFaceBottomUp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import pi 3 | 4 | from OCC.TopAbs import * 5 | from OCC.TopExp import * 6 | from OCC.BRepPrimAPI import BRepPrimAPI_MakeBox 7 | from OCC.AIS import * 8 | from OCC.Quantity import * 9 | from OCC.Display.SimpleGui import init_display 10 | from OCC.TopoDS import * 11 | from OCC.gp import * 12 | from OCC.TopLoc import * 13 | from OCC.Geom import * 14 | from OCC.BRep import * 15 | from OCC.GCE2d import * 16 | from OCC.BRepBuilderAPI import * 17 | from OCC.GC import * 18 | from OCCUtils import Topology 19 | import aocutils.brep.solid_make 20 | 21 | display, start_display, add_menu, add_function_to_menu = init_display() 22 | 23 | 24 | def geom_plane_from_face(aFace): 25 | """ 26 | Returns the geometric plane entity from a planar surface 27 | """ 28 | return Handle_Geom_Plane.DownCast(OCC.BRep.BRep_Tool_Surface(aFace)).GetObject() 29 | 30 | def redraw(shape, event=None): 31 | # display with crisp edges and transpaarency 32 | context = display.Context 33 | context.RemoveAll() 34 | context.SetAutoActivateSelection(False) 35 | aisShape = AIS_Shape(shape) 36 | h_aisShape = aisShape.GetHandle() 37 | context.Display(h_aisShape) 38 | context.SetTransparency(h_aisShape, .1) 39 | context.HilightWithColor(h_aisShape, OCC.Quantity.Quantity_NOC_BLACK) 40 | display.FitAll() 41 | 42 | def makeBox(event=None): 43 | # Make a box 44 | Box = BRepPrimAPI_MakeBox(60, 60, 50).Shape() 45 | redraw() 46 | 47 | def rotateBox(): 48 | aisShape = AIS_Shape(Box) 49 | ax1 = gp_Ax1(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)) 50 | aRotTrsf = gp_Trsf() 51 | angle = pi/6 52 | aRotTrsf.SetRotation(ax1, angle) 53 | aTopLoc = TopLoc_Location(aRotTrsf) 54 | Box.Move(aTopLoc) 55 | redraw() 56 | 57 | def enableFaceSelect(event=None): 58 | display.selected_shape = None 59 | display.SetSelectionModeFace() 60 | 61 | def makeSqProfile(size, surface): 62 | # points and segments need to be in CW sequence to get W pointing along Z 63 | aPnt1 = gp_Pnt2d(-size, size) 64 | aPnt2 = gp_Pnt2d(size, size) 65 | aPnt3 = gp_Pnt2d(size, -size) 66 | aPnt4 = gp_Pnt2d(-size, -size) 67 | aSegment1 = GCE2d_MakeSegment(aPnt1, aPnt2) 68 | aSegment2 = GCE2d_MakeSegment(aPnt2, aPnt3) 69 | aSegment3 = GCE2d_MakeSegment(aPnt3, aPnt4) 70 | aSegment4 = GCE2d_MakeSegment(aPnt4, aPnt1) 71 | print 'Next is where something crashes' 72 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value(), 73 | Handle_Geom_Surface(surface)) 74 | aEdge2 = BRepBuilderAPI_MakeEdge(aSegment2.Value(), 75 | Handle_Geom_Surface(surface)) 76 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment3.Value(), 77 | Handle_Geom_Surface(surface)) 78 | aEdge4 = BRepBuilderAPI_MakeEdge(aSegment4.Value(), 79 | Handle_Geom_Surface(surface)) 80 | print "Doesn't get here (with rotated box)" 81 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 82 | aEdge2.Edge(), 83 | aEdge3.Edge(), 84 | aEdge4.Edge()) 85 | 86 | myWireProfile = aWire.Wire() 87 | return myWireProfile # TopoDS_Wire 88 | 89 | def wireProfileOnFace(event=None): 90 | aShape = display.GetSelectedShape() 91 | shapes = display.GetSelectedShapes() 92 | face = None 93 | if aShape: 94 | face = topods_Face(aShape) 95 | print "A shape found:" 96 | elif shapes: 97 | aShape = shapes[0] 98 | face = topods_Face(aShape) 99 | print len(shapes), "Shapes found" 100 | if face: 101 | surface = geom_plane_from_face(face) 102 | wireProfile = makeSqProfile(50, surface) 103 | display.DisplayShape(wireProfile) 104 | else: 105 | print 'no face' 106 | 107 | def translatePnt(p1, vec): 108 | p2 = gp_Pnt() 109 | p2 = p1.Translated(vec) 110 | return p2 111 | 112 | def pointsToWire(p1, p2, p3, p4): 113 | seg1 = GC_MakeSegment(p1, p2) 114 | seg2 = GC_MakeSegment(p2, p3) 115 | seg3 = GC_MakeSegment(p3, p4) 116 | seg4 = GC_MakeSegment(p4, p1) 117 | edge1 = BRepBuilderAPI_MakeEdge(seg1.Value()) 118 | edge2 = BRepBuilderAPI_MakeEdge(seg2.Value()) 119 | edge3 = BRepBuilderAPI_MakeEdge(seg3.Value()) 120 | edge4 = BRepBuilderAPI_MakeEdge(seg4.Value()) 121 | wire = BRepBuilderAPI_MakeWire(edge1.Edge(), edge2.Edge(), 122 | edge3.Edge(), edge4.Edge()) 123 | return wire.Wire() 124 | 125 | def sewBox(): 126 | # Length of shape (spine) 127 | Vec = gp_Vec(0, 0, 10) 128 | # starting with bot vertices, make bot wire & face 129 | p1 = gp_Pnt(0, 0, 0) 130 | p2 = gp_Pnt(20, 0, 0) 131 | p3 = gp_Pnt(20, 20, 0) 132 | p4 = gp_Pnt(0, 20, 0) 133 | botWire = pointsToWire(p1, p2, p3, p4) 134 | botFace = BRepBuilderAPI_MakeFace(botWire).Face() 135 | # starting with topvertices, make top face 136 | p5 = translatePnt(p1, Vec) 137 | p6 = translatePnt(p2, Vec) 138 | p7 = translatePnt(p3, Vec) 139 | p8 = translatePnt(p4, Vec) 140 | topWire = pointsToWire(p5, p6, p7, p8) 141 | topFace = BRepBuilderAPI_MakeFace(topWire).Face() 142 | # Make spine (wire) to make 'pipe' 143 | spineSeg = GC_MakeSegment(p1, p5) 144 | spineEdge = BRepBuilderAPI_MakeEdge(spineSeg.Value()) 145 | spineWire = BRepBuilderAPI_MakeWire(spineEdge.Edge()).Wire() 146 | pipe = OCC.BRepOffsetAPI.BRepOffsetAPI_MakePipe(botWire, spineWire).Shape() 147 | # Sew together botFace, pipe, and topFace to get solid 148 | tolerance = 1e-6 149 | sew = OCC.BRepBuilderAPI.BRepBuilderAPI_Sewing(tolerance) 150 | sew.Add(botFace) 151 | sew.Add(pipe) 152 | sew.Add(topFace) 153 | sew.Perform() 154 | res = sew.SewedShape() 155 | redraw(res) 156 | 157 | def buildFaceBotUp(): 158 | """Following procedure in Roman Lygen's blog 159 | """ 160 | origin = gp_Pnt(0,0,0) 161 | wDir = gp_Dir(0,0,1) 162 | uDir = gp_Dir(1,0,0) 163 | vDir = gp_Dir(0,1,0) 164 | xyzAx3 = gp_Ax3(origin, wDir, uDir) 165 | gpPlane = gp_Pln(xyzAx3) # type OCC.gp.gp_Pln 166 | # gpPlane = gp_XOY() # type gp_Ax2 167 | plane = Geom_Plane(gpPlane) # type: OCC.Geom.Geom_Plane 168 | aSurf = Handle_Geom_Surface(plane) # type: Handle_Geom_Surface 169 | anExtC = Geom_Circle(gp_XOY(), 10.0) # type: OCC.Geom.Geom_Circle 170 | anIntC = Geom_Circle(gp_XOY(), 5.0) # type: OCC.Geom.Geom_Circle 171 | anExtE = BRepBuilderAPI_MakeEdge(anExtC.GetHandle()) 172 | anIntE = BRepBuilderAPI_MakeEdge(anIntC.GetHandle()) 173 | anExtWire = BRepBuilderAPI_MakeWire(anExtE.Edge()) 174 | anIntWire = BRepBuilderAPI_MakeWire(anIntE.Edge()) 175 | aFace = BRepBuilderAPI_MakeFace(anExtWire.Wire()) 176 | aFace.Add(anIntWire.Wire()) # adds wire to the face as a hole 177 | display.DisplayShape(aFace.Face()) 178 | edgeList = [] 179 | anEdgeExplorer = TopExp_Explorer(aFace.Face(), TopAbs_EDGE) 180 | while anEdgeExplorer.More(): 181 | anEdge = topods.Edge(anEdgeExplorer.Current()) 182 | anEdgeExplorer.Next() 183 | edgeList.append(anEdge) 184 | print 'Number of edges: ', len(edgeList) 185 | Topology.dumpTopology(aFace.Face()) 186 | topo = Topology.Topo(aFace.Face()) 187 | print 'Number of wires: ', topo.number_of_wires_from_face(aFace.Face()) 188 | wires = topo.wires_from_face(aFace.Face()) 189 | for wire in wires: 190 | display.DisplayShape(wire) 191 | 192 | def testEdge(): 193 | origin = gp_Pnt(0,0,0) 194 | wDir = gp_Dir(0,0,1) 195 | uDir = gp_Dir(1,0,0) 196 | vDir = gp_Dir(0,1,0) 197 | xyzAx3 = gp_Ax3(origin, wDir, uDir) 198 | gpPlane = gp_Pln(xyzAx3) # type OCC.gp.gp_Pln 199 | # gpPlane = gp_XOY() # type gp_Ax2 200 | plane = Geom_Plane(gpPlane) # type: OCC.Geom.Geom_Plane 201 | aSurf = Handle_Geom_Surface(plane) # type: Handle_Geom_Surface 202 | anExtC = Geom_Circle(gp_XOY(), 10.0) # type: OCC.Geom.Geom_Circle 203 | anIntC = Geom_Circle(gp_XOY(), 5.0) # type: OCC.Geom.Geom_Circle 204 | anExtE = BRepBuilderAPI_MakeEdge(anExtC.GetHandle()) 205 | anIntE = BRepBuilderAPI_MakeEdge(anIntC.GetHandle()) 206 | anExtWire = BRepBuilderAPI_MakeWire(anExtE.Edge()) 207 | anIntWire = BRepBuilderAPI_MakeWire(anIntE.Edge()) 208 | aFace = BRepBuilderAPI_MakeFace(anExtWire.Wire()) 209 | aFace.Add(anIntWire.Wire()) # adds wire to the face as a hole 210 | display.DisplayShape(aFace.Face()) 211 | edgeList = [] 212 | anEdgeExplorer = TopExp_Explorer(aFace.Face(), TopAbs_EDGE) 213 | while anEdgeExplorer.More(): 214 | anEdge = topods.Edge(anEdgeExplorer.Current()) 215 | 216 | anEdgeExplorer.Next() 217 | edgeList.append(anEdge) 218 | print 'Number of edges: ', len(edgeList) 219 | for edge in edgeList: 220 | hCurve, umin, umax = BRep_Tool.Curve(edge) 221 | curve = hCurve.GetObject() # type: Geom_Curve 222 | print 'umin = ', umin 223 | print 'umax = ', umax 224 | print 'Is Periodic? ', curve.IsPeriodic() 225 | print 'Shape: ', curve.Continuity() 226 | print 'first: ', curve.FirstParameter() 227 | print 'last: ', curve.LastParameter() 228 | vectr = curve.DN(0.0, 2) 229 | print 'curvature at u=0: ', vectr.Magnitude() 230 | 231 | seg = GC_MakeSegment(gp_Pnt(0,0,0), gp_Pnt(2,2,2)) 232 | edg = BRepBuilderAPI_MakeEdge(seg.Value()).Edge() 233 | print type(edg) 234 | hCurve, umin, umax = BRep_Tool.Curve(edg) 235 | curve = hCurve.GetObject() 236 | print type(curve) # type: Geom_Curve 237 | print 'Is Periodic? ', curve.IsPeriodic() 238 | print 'Shape: ', curve.Continuity() 239 | print 'first: ', curve.FirstParameter() 240 | print 'last: ', curve.LastParameter() 241 | vectr = curve.DN(0.0, 2) 242 | print 'curvature at u=0: ', vectr.Magnitude() 243 | 244 | 245 | def exit(event=None): 246 | sys.exit() 247 | 248 | if __name__ == '__main__': 249 | add_menu('operations') 250 | add_function_to_menu('operations', makeBox) 251 | add_function_to_menu('operations', rotateBox) 252 | add_function_to_menu('operations', enableFaceSelect) 253 | add_function_to_menu('operations', wireProfileOnFace) 254 | add_function_to_menu('operations', exit) 255 | add_menu('Experimental') 256 | add_function_to_menu('Experimental', sewBox) 257 | add_function_to_menu('Experimental', buildFaceBotUp) 258 | add_function_to_menu('Experimental', testEdge) 259 | 260 | start_display() 261 | -------------------------------------------------------------------------------- /misc/circleexample.py: -------------------------------------------------------------------------------- 1 | from OCC.gp import gp_Pnt, gp_Pnt2d, gp_OX2d 2 | from OCC.Geom2d import Geom2d_Circle 3 | from OCC.Geom2dAdaptor import Geom2dAdaptor_Curve 4 | from OCC.GCPnts import GCPnts_UniformAbscissa 5 | 6 | from OCC.Display.SimpleGui import init_display 7 | display, start_display, add_menu, add_function_to_menu = init_display() 8 | 9 | 10 | def points_from_curve(): 11 | radius = 5. 12 | abscissa = 3. 13 | circle = Geom2d_Circle(gp_OX2d(), radius, True) 14 | gac = Geom2dAdaptor_Curve(circle.GetHandle()) 15 | ua = GCPnts_UniformAbscissa(gac, abscissa) 16 | a_sequence = [] 17 | if ua.IsDone(): 18 | n = ua.NbPoints() 19 | for count in range(1, n + 1): 20 | p = gp_Pnt2d() 21 | circle.D0(ua.Parameter(count), p) 22 | a_sequence.append(p) 23 | # convert analytic to bspline 24 | display.DisplayShape(circle, update=True) 25 | i = 0 26 | for p in a_sequence: 27 | i = i + 1 28 | pstring = 'P%i : parameter %f' % (i, ua.Parameter(i)) 29 | pnt = gp_Pnt(p.X(), p.Y(), 0) 30 | # display points 31 | display.DisplayShape(pnt, update=True) 32 | display.DisplayMessage(pnt, pstring) 33 | 34 | if __name__ == '__main__': 35 | points_from_curve() 36 | start_display() 37 | -------------------------------------------------------------------------------- /misc/core_topology_local_ops.py: -------------------------------------------------------------------------------- 1 | ##Copyright 2009-2016 Thomas Paviot (tpaviot@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 | import sys 18 | from math import pi 19 | 20 | from OCC.BRep import BRep_Tool_Surface 21 | from OCC.BRepAlgoAPI import BRepAlgoAPI_Section, BRepAlgoAPI_Fuse 22 | from OCC.BRepBuilderAPI import BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeFace, \ 23 | BRepBuilderAPI_GTransform 24 | from OCC.BRepFeat import BRepFeat_MakePrism, BRepFeat_MakeDPrism, BRepFeat_SplitShape, \ 25 | BRepFeat_MakeLinearForm, BRepFeat_MakeRevol 26 | from OCC.BRepLib import breplib_BuildCurves3d 27 | from OCC.BRepOffset import BRepOffset_Skin 28 | from OCC.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid, BRepOffsetAPI_MakeOffsetShape 29 | from OCC.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakePrism 30 | from OCC.Display.SimpleGui import init_display 31 | from OCC.GCE2d import GCE2d_MakeLine 32 | from OCC.Geom import Handle_Geom_Plane_DownCast, Geom_Plane 33 | from OCC.Geom2d import Geom2d_Circle 34 | from OCC.GeomAbs import GeomAbs_Arc 35 | from OCC.TopTools import TopTools_ListOfShape 36 | from OCC.TopoDS import TopoDS_Face 37 | from OCC.gp import gp_Pnt2d, gp_Circ2d, gp_Ax2d, gp_Dir2d, gp_Pnt, gp_Pln, gp_Vec, gp_OX, gp_Trsf, gp_GTrsf 38 | 39 | from core_topology_traverse import Topo 40 | 41 | display, start_display, add_menu, add_function_to_menu = init_display() 42 | 43 | 44 | def extrusion(event=None): 45 | # Make a box 46 | Box = BRepPrimAPI_MakeBox(400., 250., 300.) 47 | S = Box.Shape() 48 | 49 | # Choose the first Face of the box 50 | F = next(Topo(S).faces()) 51 | surf = BRep_Tool_Surface(F) 52 | 53 | # Make a plane from this face 54 | Pl = Handle_Geom_Plane_DownCast(surf) 55 | Pln = Pl.GetObject() 56 | 57 | # Get the normal of this plane. This will be the direction of extrusion. 58 | D = Pln.Axis().Direction() 59 | 60 | # Inverse normal 61 | #D.Reverse() 62 | 63 | # Create the 2D planar sketch 64 | MW = BRepBuilderAPI_MakeWire() 65 | p1 = gp_Pnt2d(200., -100.) 66 | p2 = gp_Pnt2d(100., -100.) 67 | aline = GCE2d_MakeLine(p1, p2).Value() 68 | Edge1 = BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)) 69 | MW.Add(Edge1.Edge()) 70 | p1 = p2 71 | p2 = gp_Pnt2d(100., -200.) 72 | aline = GCE2d_MakeLine(p1, p2).Value() 73 | Edge2 = BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)) 74 | MW.Add(Edge2.Edge()) 75 | p1 = p2 76 | p2 = gp_Pnt2d(200., -200.) 77 | aline = GCE2d_MakeLine(p1, p2).Value() 78 | Edge3 = BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)) 79 | MW.Add(Edge3.Edge()) 80 | p1 = p2 81 | p2 = gp_Pnt2d(200., -100.) 82 | aline = GCE2d_MakeLine(p1, p2).Value() 83 | Edge4 = BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)) 84 | MW.Add(Edge4.Edge()) 85 | 86 | # Build Face from Wire. NB: a face is required to generate a solid. 87 | MKF = BRepBuilderAPI_MakeFace() 88 | MKF.Init(surf, False, 1e-6) 89 | MKF.Add(MW.Wire()) 90 | FP = MKF.Face() 91 | breplib_BuildCurves3d(FP) 92 | 93 | MKP = BRepFeat_MakePrism(S, FP, F, D, False, True) 94 | MKP.Perform(200.) 95 | # TODO MKP completes, seeing a split operation but no extrusion 96 | assert MKP.IsDone() 97 | res1 = MKP.Shape() 98 | 99 | display.EraseAll() 100 | display.DisplayColoredShape(res1, 'BLUE') 101 | display.DisplayColoredShape(FP, 'YELLOW') 102 | display.FitAll() 103 | 104 | 105 | def brepfeat_prism(event=None): 106 | box = BRepPrimAPI_MakeBox(400, 250, 300).Shape() 107 | faces = Topo(box).faces() 108 | 109 | for i in range(3): 110 | face = next(faces) 111 | 112 | srf = BRep_Tool_Surface(face) 113 | 114 | c = gp_Circ2d(gp_Ax2d(gp_Pnt2d(200, 130), 115 | gp_Dir2d(1, 0)), 75) 116 | 117 | circle = Geom2d_Circle(c).GetHandle() 118 | 119 | wire = BRepBuilderAPI_MakeWire() 120 | wire.Add(BRepBuilderAPI_MakeEdge(circle, srf, 0., pi).Edge()) 121 | wire.Add(BRepBuilderAPI_MakeEdge(circle, srf, pi, 2. * pi).Edge()) 122 | wire.Build() 123 | 124 | display.DisplayShape(wire.Wire()) 125 | 126 | mkf = BRepBuilderAPI_MakeFace() 127 | mkf.Init(srf, False, 1e-6) 128 | mkf.Add(wire.Wire()) 129 | mkf.Build() 130 | 131 | new_face = mkf.Face() 132 | breplib_BuildCurves3d(new_face) 133 | 134 | display.DisplayColoredShape(box, 'GREEN') 135 | display.DisplayShape(new_face) 136 | """ 137 | prism = BRepFeat_MakeDPrism(box, mkf.Face(), face, 100, True, True) 138 | 139 | prism.Perform(400) 140 | assert prism.IsDone() 141 | display.EraseAll() 142 | display.DisplayShape(prism.Shape()) 143 | """ 144 | #display.DisplayColoredShape(wire.Wire(), 'RED') 145 | display.FitAll() 146 | 147 | 148 | def thick_solid(event=None): 149 | S = BRepPrimAPI_MakeBox(150, 200, 110).Shape() 150 | 151 | topo = Topo(S) 152 | vert = next(topo.vertices()) 153 | 154 | shapes = TopTools_ListOfShape() 155 | for f in topo.faces_from_vertex(vert): 156 | shapes.Append(f) 157 | 158 | _thick_solid = BRepOffsetAPI_MakeThickSolid(S, shapes, 15, 0.01) 159 | display.EraseAll() 160 | display.DisplayShape(_thick_solid.Shape()) 161 | display.FitAll() 162 | 163 | 164 | def offset_cube(event=None): 165 | S2 = BRepPrimAPI_MakeBox(gp_Pnt(300, 0, 0), 220, 140, 180).Shape() 166 | offsetB = BRepOffsetAPI_MakeOffsetShape(S2, -20, 0.01, BRepOffset_Skin, False, False, GeomAbs_Arc) 167 | offB = display.DisplayColoredShape(S2, 'BLUE') 168 | display.Context.SetTransparency(offB, 0.3) 169 | display.DisplayColoredShape(offsetB.Shape(), 'GREEN') 170 | display.FitAll() 171 | 172 | 173 | def split_shape(event=None): 174 | S = BRepPrimAPI_MakeBox(gp_Pnt(-100, -60, -80), 150, 200, 170).Shape() 175 | asect = BRepAlgoAPI_Section(S, gp_Pln(1, 2, 1, -15), False) 176 | asect.ComputePCurveOn1(True) 177 | asect.Approximation(True) 178 | asect.Build() 179 | R = asect.Shape() 180 | 181 | asplit = BRepFeat_SplitShape(S) 182 | 183 | for edg in Topo(R).edges(): 184 | face = TopoDS_Face() 185 | if asect.HasAncestorFaceOn1(edg, face): 186 | asplit.Add(edg, face) 187 | 188 | asplit.Build() 189 | display.EraseAll() 190 | display.DisplayShape(asplit.Shape()) 191 | display.FitAll() 192 | 193 | 194 | def brep_feat_rib(event=None): 195 | mkw = BRepBuilderAPI_MakeWire() 196 | 197 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0., 0., 0.), gp_Pnt(200., 0., 0.)).Edge()) 198 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(200., 0., 0.), gp_Pnt(200., 0., 50.)).Edge()) 199 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(200., 0., 50.), gp_Pnt(50., 0., 50.)).Edge()) 200 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(50., 0., 50.), gp_Pnt(50., 0., 200.)).Edge()) 201 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(50., 0., 200.), gp_Pnt(0., 0., 200.)).Edge()) 202 | mkw.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0., 0., 200.), gp_Pnt(0., 0., 0.)).Edge()) 203 | 204 | S = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(mkw.Wire()).Face(), 205 | gp_Vec(gp_Pnt(0., 0., 0.), 206 | gp_Pnt(0., 100., 0.))) 207 | display.EraseAll() 208 | # display.DisplayShape(S.Shape()) 209 | 210 | W = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(gp_Pnt(50., 45., 100.), 211 | gp_Pnt(100., 45., 50.)).Edge()) 212 | 213 | aplane = Geom_Plane(0., 1., 0., -45.) 214 | 215 | aform = BRepFeat_MakeLinearForm(S.Shape(), W.Wire(), aplane.GetHandle(), 216 | gp_Vec(0., 10., 0.), gp_Vec(0., 0., 0.), 217 | 1, True) 218 | aform.Perform() 219 | display.DisplayShape(aform.Shape()) 220 | display.FitAll() 221 | 222 | 223 | def brep_feat_local_revolution(event=None): 224 | S = BRepPrimAPI_MakeBox(400., 250., 300.).Shape() 225 | faces = list(Topo(S).faces()) 226 | F1 = faces[2] 227 | surf = BRep_Tool_Surface(F1) 228 | 229 | D = gp_OX() 230 | 231 | MW1 = BRepBuilderAPI_MakeWire() 232 | p1 = gp_Pnt2d(100., 100.) 233 | p2 = gp_Pnt2d(200., 100.) 234 | aline = GCE2d_MakeLine(p1, p2).Value() 235 | MW1.Add(BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)).Edge()) 236 | 237 | p1 = gp_Pnt2d(200., 100.) 238 | p2 = gp_Pnt2d(150., 200.) 239 | aline = GCE2d_MakeLine(p1, p2).Value() 240 | MW1.Add(BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)).Edge()) 241 | 242 | p1 = gp_Pnt2d(150., 200.) 243 | p2 = gp_Pnt2d(100., 100.) 244 | aline = GCE2d_MakeLine(p1, p2).Value() 245 | MW1.Add(BRepBuilderAPI_MakeEdge(aline, surf, 0., p1.Distance(p2)).Edge()) 246 | 247 | MKF1 = BRepBuilderAPI_MakeFace() 248 | MKF1.Init(surf, False, 1e-6) 249 | MKF1.Add(MW1.Wire()) 250 | FP = MKF1.Face() 251 | breplib_BuildCurves3d(FP) 252 | MKrev = BRepFeat_MakeRevol(S, FP, F1, D, 1, True) 253 | F2 = faces[4] 254 | MKrev.Perform(F2) 255 | display.EraseAll() 256 | display.DisplayShape(MKrev.Shape()) 257 | display.FitAll() 258 | 259 | 260 | def brep_feat_extrusion_protrusion(event=None): 261 | # Extrusion 262 | S = BRepPrimAPI_MakeBox(400., 250., 300.).Shape() 263 | faces = Topo(S).faces() 264 | F = next(faces) 265 | surf1 = BRep_Tool_Surface(F) 266 | 267 | Pl1 = Handle_Geom_Plane_DownCast(surf1).GetObject() 268 | 269 | D1 = Pl1.Pln().Axis().Direction().Reversed() 270 | MW = BRepBuilderAPI_MakeWire() 271 | p1, p2 = gp_Pnt2d(200., -100.), gp_Pnt2d(100., -100.) 272 | aline = GCE2d_MakeLine(p1, p2).Value() 273 | MW.Add(BRepBuilderAPI_MakeEdge(aline, surf1, 0., p1.Distance(p2)).Edge()) 274 | 275 | p1, p2 = gp_Pnt2d(100., -100.), gp_Pnt2d(100., -200.) 276 | aline = GCE2d_MakeLine(p1, p2).Value() 277 | MW.Add(BRepBuilderAPI_MakeEdge(aline, surf1, 0., p1.Distance(p2)).Edge()) 278 | 279 | p1, p2 = gp_Pnt2d(100., -200.), gp_Pnt2d(200., -200.) 280 | aline = GCE2d_MakeLine(p1, p2).Value() 281 | MW.Add(BRepBuilderAPI_MakeEdge(aline, surf1, 0., p1.Distance(p2)).Edge()) 282 | 283 | p1, p2 = gp_Pnt2d(200., -200.), gp_Pnt2d(200., -100.) 284 | aline = GCE2d_MakeLine(p1, p2).Value() 285 | MW.Add(BRepBuilderAPI_MakeEdge(aline, surf1, 0., p1.Distance(p2)).Edge()) 286 | 287 | MKF = BRepBuilderAPI_MakeFace() 288 | MKF.Init(surf1, False, 1e-6) 289 | MKF.Add(MW.Wire()) 290 | FP = MKF.Face() 291 | breplib_BuildCurves3d(FP) 292 | 293 | display.EraseAll() 294 | MKP = BRepFeat_MakePrism(S, FP, F, D1, 0, True) 295 | MKP.PerformThruAll() 296 | 297 | res1 = MKP.Shape() 298 | display.DisplayShape(res1) 299 | 300 | # Protrusion 301 | next(faces) 302 | F2 = next(faces) 303 | surf2 = BRep_Tool_Surface(F2) 304 | Pl2 = Handle_Geom_Plane_DownCast(surf2).GetObject() 305 | D2 = Pl2.Pln().Axis().Direction().Reversed() 306 | MW2 = BRepBuilderAPI_MakeWire() 307 | p1, p2 = gp_Pnt2d(100., 100.), gp_Pnt2d(200., 100.) 308 | aline = GCE2d_MakeLine(p1, p2).Value() 309 | MW2.Add(BRepBuilderAPI_MakeEdge(aline, surf2, 0., p1.Distance(p2)).Edge()) 310 | 311 | p1, p2 = gp_Pnt2d(200., 100.), gp_Pnt2d(150., 200.) 312 | aline = GCE2d_MakeLine(p1, p2).Value() 313 | MW2.Add(BRepBuilderAPI_MakeEdge(aline, surf2, 0., p1.Distance(p2)).Edge()) 314 | 315 | p1, p2 = gp_Pnt2d(150., 200.), gp_Pnt2d(100., 100.) 316 | aline = GCE2d_MakeLine(p1, p2).Value() 317 | MW2.Add(BRepBuilderAPI_MakeEdge(aline, surf2, 0., p1.Distance(p2)).Edge()) 318 | 319 | MKF2 = BRepBuilderAPI_MakeFace() 320 | MKF2.Init(surf2, False, 1e-6) 321 | MKF2.Add(MW2.Wire()) 322 | MKF2.Build() 323 | 324 | FP = MKF2.Face() 325 | breplib_BuildCurves3d(FP) 326 | MKP2 = BRepFeat_MakePrism(res1, FP, F2, D2, 0, True) 327 | MKP2.PerformThruAll() 328 | display.EraseAll() 329 | 330 | trf = gp_Trsf() 331 | trf.SetTranslation(gp_Vec(0, 0, 300)) 332 | gtrf = gp_GTrsf() 333 | gtrf.SetTrsf(trf) 334 | tr = BRepBuilderAPI_GTransform(MKP2.Shape(), gtrf, True) 335 | 336 | fused = BRepAlgoAPI_Fuse(tr.Shape(), MKP2.Shape()) 337 | fused.RefineEdges() 338 | fused.Build() 339 | print('Boolean operation error status:', fused.ErrorStatus()) 340 | display.DisplayShape(fused.Shape()) 341 | display.FitAll() 342 | 343 | def exit(event=None): 344 | sys.exit() 345 | 346 | 347 | if __name__ == '__main__': 348 | add_menu('topology local operations') 349 | add_function_to_menu('topology local operations', brepfeat_prism) 350 | add_function_to_menu('topology local operations', extrusion) 351 | add_function_to_menu('topology local operations', thick_solid) 352 | add_function_to_menu('topology local operations', offset_cube) 353 | add_function_to_menu('topology local operations', split_shape) 354 | add_function_to_menu('topology local operations', brep_feat_rib) 355 | add_function_to_menu('topology local operations', brep_feat_local_revolution) 356 | add_function_to_menu('topology local operations', brep_feat_extrusion_protrusion) 357 | add_function_to_menu('topology local operations', exit) 358 | start_display() 359 | -------------------------------------------------------------------------------- /misc/example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import pi 3 | 4 | from OCC.BRepPrimAPI import BRepPrimAPI_MakeBox 5 | from OCC.AIS import * 6 | from OCC.Quantity import * 7 | from OCC.Display.SimpleGui import init_display 8 | from OCC.TopoDS import * 9 | from OCC.gp import * 10 | from OCC.TopLoc import * 11 | from OCC.Geom import * 12 | from OCC.BRep import BRep_Tool_Surface 13 | from OCC.GCE2d import * 14 | from OCC.BRepBuilderAPI import * 15 | 16 | display, start_display, add_menu, add_function_to_menu = init_display() 17 | 18 | 19 | def geom_plane_from_face(aFace): 20 | """ 21 | Returns the geometric plane entity from a planar surface 22 | """ 23 | return Handle_Geom_Plane.DownCast(OCC.BRep.BRep_Tool_Surface(aFace)).GetObject() 24 | 25 | def redraw(event=None): 26 | # display with crisp edges and transpaarency 27 | context = display.Context 28 | context.RemoveAll() 29 | context.SetAutoActivateSelection(False) 30 | aisShape = AIS_Shape(Box) 31 | h_aisShape = aisShape.GetHandle() 32 | context.Display(h_aisShape) 33 | context.SetTransparency(h_aisShape, .6) 34 | context.HilightWithColor(h_aisShape, OCC.Quantity.Quantity_NOC_BLACK) 35 | display.FitAll() 36 | 37 | def makeBox(event=None): 38 | global Box 39 | # Make a box 40 | Box = BRepPrimAPI_MakeBox(60, 60, 50).Shape() 41 | redraw() 42 | 43 | def rotateBox(): 44 | aisShape = AIS_Shape(Box) 45 | ax1 = gp_Ax1(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)) 46 | aRotTrsf = gp_Trsf() 47 | angle = pi/6 48 | aRotTrsf.SetRotation(ax1, angle) 49 | aTopLoc = TopLoc_Location(aRotTrsf) 50 | Box.Move(aTopLoc) 51 | redraw() 52 | 53 | def enableFaceSelect(event=None): 54 | display.selected_shape = None 55 | display.SetSelectionModeFace() 56 | 57 | def makeSqProfile(size, surface): 58 | # points and segments need to be in CW sequence to get W pointing along Z 59 | aPnt1 = gp_Pnt2d(-size, size) 60 | aPnt2 = gp_Pnt2d(size, size) 61 | aPnt3 = gp_Pnt2d(size, -size) 62 | aPnt4 = gp_Pnt2d(-size, -size) 63 | aSegment1 = GCE2d_MakeSegment(aPnt1, aPnt2) 64 | aSegment2 = GCE2d_MakeSegment(aPnt2, aPnt3) 65 | aSegment3 = GCE2d_MakeSegment(aPnt3, aPnt4) 66 | aSegment4 = GCE2d_MakeSegment(aPnt4, aPnt1) 67 | print 'Next is where something crashes' 68 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value(), 69 | Handle_Geom_Surface(surface)) 70 | aEdge2 = BRepBuilderAPI_MakeEdge(aSegment2.Value(), 71 | Handle_Geom_Surface(surface)) 72 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment3.Value(), 73 | Handle_Geom_Surface(surface)) 74 | aEdge4 = BRepBuilderAPI_MakeEdge(aSegment4.Value(), 75 | Handle_Geom_Surface(surface)) 76 | print "Doesn't get here (with rotated box)" 77 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 78 | aEdge2.Edge(), 79 | aEdge3.Edge(), 80 | aEdge4.Edge()) 81 | 82 | myWireProfile = aWire.Wire() 83 | return myWireProfile # TopoDS_Wire 84 | 85 | def wireProfileOnFace(event=None): 86 | aShape = display.GetSelectedShape() 87 | shapes = display.GetSelectedShapes() 88 | face = None 89 | if aShape: 90 | face = topods_Face(aShape) 91 | print "A shape found:" 92 | elif shapes: 93 | aShape = shapes[0] 94 | face = topods_Face(aShape) 95 | print len(shapes), "Shapes found" 96 | if face: 97 | surface = geom_plane_from_face(face) 98 | wireProfile = makeSqProfile(50, surface) 99 | display.DisplayShape(wireProfile) 100 | else: 101 | print 'no face' 102 | 103 | def exit(event=None): 104 | sys.exit() 105 | 106 | if __name__ == '__main__': 107 | add_menu('operations') 108 | add_function_to_menu('operations', makeBox) 109 | add_function_to_menu('operations', rotateBox) 110 | add_function_to_menu('operations', enableFaceSelect) 111 | add_function_to_menu('operations', wireProfileOnFace) 112 | add_function_to_menu('operations', exit) 113 | start_display() 114 | -------------------------------------------------------------------------------- /misc/example1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Figured out what the problem was, after studying 3 | pythonocc-core/examples/core_topology_local_ops.py 4 | 5 | BRepBuilderAPI_MakeEdge(aSegment1.Value(), Handle_Geom_Surface(surface)) 6 | needs a 'surface' and I was feeding it a Geom_Plane. 7 | """ 8 | import sys 9 | from math import pi 10 | 11 | from OCC.BRepPrimAPI import BRepPrimAPI_MakeBox 12 | from OCC.AIS import * 13 | from OCC.Quantity import * 14 | from OCC.Display.SimpleGui import init_display 15 | from OCC.TopoDS import * 16 | from OCC.gp import * 17 | from OCC.TopLoc import * 18 | from OCC.Geom import * 19 | from OCC.BRep import BRep_Tool_Surface 20 | from OCC.GCE2d import * 21 | from OCC.BRepBuilderAPI import * 22 | 23 | display, start_display, add_menu, add_function_to_menu = init_display() 24 | context = display.Context 25 | 26 | def geom_plane_from_face(aFace): 27 | """ 28 | Returns the geometric plane entity from a planar surface 29 | """ 30 | return Handle_Geom_Plane_DownCast(OCC.BRep.BRep_Tool_Surface(aFace)).GetObject() 31 | 32 | def surface_from_face(aFace): 33 | """ 34 | Returns a surface entity from a face 35 | """ 36 | return OCC.BRep.BRep_Tool_Surface(aFace) 37 | 38 | 39 | def redraw(event=None): 40 | # display with crisp edges and transparency 41 | context.RemoveAll() 42 | context.SetAutoActivateSelection(False) 43 | aisShape = AIS_Shape(Box) 44 | h_aisShape = aisShape.GetHandle() 45 | context.Display(h_aisShape) 46 | context.SetTransparency(h_aisShape, .6) 47 | context.HilightWithColor(h_aisShape, OCC.Quantity.Quantity_NOC_BLACK) 48 | display.FitAll() 49 | 50 | def makeBox(event=None): 51 | global Box 52 | # Make a box 53 | Box = BRepPrimAPI_MakeBox(60, 60, 50).Shape() 54 | redraw() 55 | 56 | def rotateBox(): 57 | aisShape = AIS_Shape(Box) 58 | ax1 = gp_Ax1(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)) 59 | aRotTrsf = gp_Trsf() 60 | angle = pi/6 61 | aRotTrsf.SetRotation(ax1, angle) 62 | aTopLoc = TopLoc_Location(aRotTrsf) 63 | Box.Move(aTopLoc) 64 | redraw() 65 | 66 | def enableFaceSelect(event=None): 67 | display.selected_shape = None 68 | display.SetSelectionModeFace() 69 | 70 | def makeSqProfile(size, surface): 71 | # points and segments need to be in CW sequence to get W pointing along Z 72 | aPnt1 = gp_Pnt2d(-size, size) 73 | aPnt2 = gp_Pnt2d(size, size) 74 | aPnt3 = gp_Pnt2d(size, -size) 75 | aPnt4 = gp_Pnt2d(-size, -size) 76 | aSegment1 = GCE2d_MakeSegment(aPnt1, aPnt2) 77 | aSegment2 = GCE2d_MakeSegment(aPnt2, aPnt3) 78 | aSegment3 = GCE2d_MakeSegment(aPnt3, aPnt4) 79 | aSegment4 = GCE2d_MakeSegment(aPnt4, aPnt1) 80 | print 'Next is where something crashes' 81 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value(), 82 | Handle_Geom_Surface(surface)) 83 | aEdge2 = BRepBuilderAPI_MakeEdge(aSegment2.Value(), 84 | Handle_Geom_Surface(surface)) 85 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment3.Value(), 86 | Handle_Geom_Surface(surface)) 87 | aEdge4 = BRepBuilderAPI_MakeEdge(aSegment4.Value(), 88 | Handle_Geom_Surface(surface)) 89 | print "Doesn't get here (with rotated box)" 90 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 91 | aEdge2.Edge(), 92 | aEdge3.Edge(), 93 | aEdge4.Edge()) 94 | 95 | myWireProfile = aWire.Wire() 96 | return myWireProfile # TopoDS_Wire 97 | 98 | def wireProfileOnFace(event=None): 99 | aShape = display.GetSelectedShape() 100 | shapes = display.GetSelectedShapes() 101 | face = None 102 | if aShape: 103 | face = topods_Face(aShape) 104 | print "A shape found:" 105 | elif shapes: 106 | aShape = shapes[0] 107 | face = topods_Face(aShape) 108 | print len(shapes), "Shapes found" 109 | if face: 110 | surface = surface_from_face(face) 111 | wireProfile = makeSqProfile(50, surface) 112 | display.DisplayShape(wireProfile) 113 | else: 114 | print 'no face' 115 | 116 | def exit(event=None): 117 | sys.exit() 118 | 119 | if __name__ == '__main__': 120 | add_menu('operations') 121 | add_function_to_menu('operations', makeBox) 122 | add_function_to_menu('operations', rotateBox) 123 | add_function_to_menu('operations', enableFaceSelect) 124 | add_function_to_menu('operations', wireProfileOnFace) 125 | add_function_to_menu('operations', exit) 126 | start_display() 127 | -------------------------------------------------------------------------------- /misc/example_updated_to_run_on_v7.4.py: -------------------------------------------------------------------------------- 1 | """ 2 | From an unanswered post on https://groups.google.com/forum/#!topic/pythonocc/O3dBbwpB5T8 3 | back on 7/29/2016 in which I reported having trouble with 'example.py'. 4 | Below, it has been revised to run on Pythonocc version "7.4.0rc1" & Python 3.7. 5 | """ 6 | import sys 7 | from math import pi 8 | 9 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox 10 | from OCC.Core.AIS import * 11 | from OCC.Core.Quantity import * 12 | from OCC.Display.SimpleGui import init_display 13 | from OCC.Core.TopoDS import * 14 | from OCC.Core.gp import * 15 | from OCC.Core.TopLoc import * 16 | from OCC.Core.Geom import * 17 | from OCC.Core.BRep import BRep_Tool_Surface 18 | from OCC.Core.GCE2d import * 19 | from OCC.Core.BRepBuilderAPI import * 20 | 21 | display, start_display, add_menu, add_function_to_menu = init_display() 22 | 23 | 24 | def geom_plane_from_face(aFace): 25 | """Return geometric plane entity from a planar surface.""" 26 | geomplane = DownCast(OCC.BRep.BRep_Tool_Surface(aFace)) 27 | print(type(geomplane)) 28 | return geomplane 29 | 30 | def redraw(event=None): 31 | # display with crisp edges and transparency 32 | context = display.Context 33 | context.RemoveAll(True) 34 | context.SetAutoActivateSelection(False) 35 | aisShape = AIS_Shape(Box) 36 | context.Display(aisShape, True) 37 | context.SetTransparency(aisShape, .6, True) 38 | drawer = aisShape.DynamicHilightAttributes() 39 | context.HilightWithColor(aisShape, drawer, True) 40 | display.FitAll() 41 | 42 | def makeBox(event=None): 43 | global Box 44 | # Make a box 45 | Box = BRepPrimAPI_MakeBox(60, 60, 50).Shape() 46 | redraw() 47 | 48 | def rotateBox(): 49 | aisShape = AIS_Shape(Box) 50 | ax1 = gp_Ax1(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)) 51 | aRotTrsf = gp_Trsf() 52 | angle = pi/6 53 | aRotTrsf.SetRotation(ax1, angle) 54 | aTopLoc = TopLoc_Location(aRotTrsf) 55 | Box.Move(aTopLoc) 56 | redraw() 57 | 58 | def enableFaceSelect(event=None): 59 | display.selected_shape = None 60 | display.SetSelectionModeFace() 61 | 62 | def makeSqProfile(size, surface): 63 | # points and segments need to be in CW sequence to get W pointing along Z 64 | aPnt1 = gp_Pnt2d(-size, size) 65 | aPnt2 = gp_Pnt2d(size, size) 66 | aPnt3 = gp_Pnt2d(size, -size) 67 | aPnt4 = gp_Pnt2d(-size, -size) 68 | aSegment1 = GCE2d_MakeSegment(aPnt1, aPnt2) 69 | aSegment2 = GCE2d_MakeSegment(aPnt2, aPnt3) 70 | aSegment3 = GCE2d_MakeSegment(aPnt3, aPnt4) 71 | aSegment4 = GCE2d_MakeSegment(aPnt4, aPnt1) 72 | print('Next is where something crashes') 73 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value(), 74 | Handle_Geom_Surface(surface)) 75 | aEdge2 = BRepBuilderAPI_MakeEdge(aSegment2.Value(), 76 | Handle_Geom_Surface(surface)) 77 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment3.Value(), 78 | Handle_Geom_Surface(surface)) 79 | aEdge4 = BRepBuilderAPI_MakeEdge(aSegment4.Value(), 80 | Handle_Geom_Surface(surface)) 81 | print("Doesn't get here (with rotated box)") 82 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 83 | aEdge2.Edge(), 84 | aEdge3.Edge(), 85 | aEdge4.Edge()) 86 | 87 | myWireProfile = aWire.Wire() 88 | return myWireProfile # TopoDS_Wire 89 | 90 | def wireProfileOnFace(event=None): 91 | aShape = display.GetSelectedShape() 92 | shapes = display.GetSelectedShapes() 93 | face = None 94 | if aShape: 95 | face = topods_Face(aShape) 96 | print("A shape found:") 97 | elif shapes: 98 | aShape = shapes[0] 99 | face = topods_Face(aShape) 100 | print(len(shapes), "Shapes found") 101 | if face: 102 | surface = geom_plane_from_face(face) 103 | wireProfile = makeSqProfile(50, surface) 104 | display.DisplayShape(wireProfile) 105 | else: 106 | print('no face') 107 | 108 | def exit(event=None): 109 | sys.exit() 110 | 111 | if __name__ == '__main__': 112 | add_menu('operations') 113 | add_function_to_menu('operations', makeBox) 114 | add_function_to_menu('operations', rotateBox) 115 | add_function_to_menu('operations', enableFaceSelect) 116 | add_function_to_menu('operations', wireProfileOnFace) 117 | add_function_to_menu('operations', exit) 118 | start_display() 119 | -------------------------------------------------------------------------------- /misc/myqtDisplay.py: -------------------------------------------------------------------------------- 1 | 2 | from OCC.Display import qtDisplay 3 | from OCC.Display.backend import get_qt_modules 4 | 5 | QtCore, QtGui, QtWidgets, QtOpenGL = get_qt_modules() 6 | 7 | class point(object): 8 | def __init__(self, obj=None): 9 | self.x = 0 10 | self.y = 0 11 | if obj is not None: 12 | self.set(obj) 13 | 14 | def set(self, obj): 15 | self.x = obj.x() 16 | self.y = obj.y() 17 | 18 | class MyqtViewer3d(qtDisplay.qtViewer3d): 19 | " Modify qtViewer3d to emit signals" 20 | 21 | def mousePressEvent(self, event): 22 | self.emit(QtCore.SIGNAL("LMBPressed")) 23 | #print 'LMBPressed signal emitted.' 24 | self.setFocus() 25 | self.dragStartPos = point(event.pos()) 26 | self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y) 27 | 28 | def mouseReleaseEvent(self, event): 29 | self.emit(QtCore.SIGNAL("LMBReleased")) 30 | #print 'LMBReleased signal emitted.' 31 | pt = point(event.pos()) 32 | modifiers = event.modifiers() 33 | 34 | if event.button() == QtCore.Qt.LeftButton: 35 | pt = point(event.pos()) 36 | if self._select_area: 37 | [Xmin, Ymin, dx, dy] = self._drawbox 38 | self._display.SelectArea(Xmin, Ymin, Xmin + dx, Ymin + dy) 39 | self._select_area = False 40 | else: 41 | # multiple select if shift is pressed 42 | if modifiers == QtCore.Qt.ShiftModifier: 43 | self._display.ShiftSelect(pt.x, pt.y) 44 | else: 45 | # single select otherwise 46 | self._display.Select(pt.x, pt.y) 47 | elif event.button() == QtCore.Qt.RightButton: 48 | if self._zoom_area: 49 | [Xmin, Ymin, dx, dy] = self._drawbox 50 | self._display.ZoomArea(Xmin, Ymin, Xmin + dx, Ymin + dy) 51 | self._zoom_area = False 52 | -------------------------------------------------------------------------------- /misc/tkrpncalc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # CADvas 4 | # A 2D CAD application written in Python and based on the Tkinter canvas. 5 | # The latest version of this file can be found at: 6 | # http://members.localnet.com/~blanding/cadvas 7 | # 8 | # Author: Doug Blanding 9 | # 10 | # CADvas is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # CADvas is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with CADvas; if not, write to the Free Software 22 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 | # 24 | 25 | from __future__ import division 26 | import sys 27 | from Tkinter import * 28 | import math 29 | 30 | def but(root, text, row, col, com=None, span=2, clr='darkslateblue', pad=1): 31 | w = Button(root, text=text, command=com, bg=clr, fg='white', padx=pad) 32 | w.grid(row=row, column=col, columnspan=span, sticky=E+W) 33 | 34 | def ent(root, var, row, col=2, span=10): 35 | e = Entry(root, textvariable=var, relief=SUNKEN) 36 | e.grid(row=row, column=col, columnspan=span) 37 | 38 | def f2s(f): 39 | """Convert float to string with 12 significant figures.""" 40 | return '%1.12f' % f 41 | 42 | class Calculator(Toplevel): 43 | """RPN (Reverse Polish Notation) calculator styled after the one 44 | used in CoCreate SolidDesigner CAD.""" 45 | mem = '' 46 | keip = False # Flag set when keyboard entry is in progress 47 | needrup = False # Flag signaling need to rotate up with next keyboard entry 48 | def __init__(self, caller=None): 49 | Toplevel.__init__(self) 50 | self.caller = caller # ref to Draw instance 51 | self.title('RPN Calc') 52 | self.protocol("WM_DELETE_WINDOW", self.quit) 53 | #self.resizable(width=0, height=0) 54 | if caller: 55 | self.transient(caller) 56 | 57 | but(self, 't', 0, 0, lambda r='t': self.pr(r), clr='dimgray') 58 | but(self, 'z', 1, 0, lambda r='z': self.pr(r), clr='dimgray') 59 | but(self, 'y', 2, 0, lambda r='y': self.pr(r), clr='dimgray') 60 | but(self, 'x', 3, 0, lambda r='x': self.pr(r), clr='dimgray') 61 | 62 | self.tdisplay = StringVar() 63 | self.zdisplay = StringVar() 64 | self.ydisplay = StringVar() 65 | self.xdisplay = StringVar() 66 | ent(self, self.tdisplay, 0) 67 | ent(self, self.zdisplay, 1) 68 | ent(self, self.ydisplay, 2) 69 | ent(self, self.xdisplay, 3) 70 | 71 | but(self, 'mm->in', 4, 0, self.mm2in, span=4, clr='dimgray') 72 | but(self, 'in->mm', 4, 4, self.in2mm, span=4, clr='dimgray') 73 | but(self, 'Sto', 4, 8, self.storex, clr='darkgreen') 74 | but(self, 'Rcl', 4, 10, self.recallx, clr='darkgreen') 75 | but(self, '7', 5, 0, lambda c='7': self.keyin(c), clr='steelblue') 76 | but(self, '8', 5, 2, lambda c='8': self.keyin(c), clr='steelblue') 77 | but(self, '9', 5, 4, lambda c='9': self.keyin(c), clr='steelblue') 78 | but(self, '+', 5, 6, lambda op='+': self.calc(op)) 79 | but(self, 'Rup', 5, 8, self.rotateup, clr='darkgreen') 80 | but(self, 'Rdn', 5, 10, self.rotatedn, clr='darkgreen') 81 | but(self, '4', 6, 0, lambda c='4': self.keyin(c), clr='steelblue') 82 | but(self, '5', 6, 2, lambda c='5': self.keyin(c), clr='steelblue') 83 | but(self, '6', 6, 4, lambda c='6': self.keyin(c), clr='steelblue') 84 | but(self, '-', 6, 6, lambda op='-': self.calc(op)) 85 | but(self, '<-', 6, 8, self.trimx, clr='darkred') 86 | but(self, 'x<>y', 6, 10, self.swapxy, clr='darkgreen', pad=0) 87 | but(self, '1', 7, 0, lambda c='1': self.keyin(c), clr='steelblue') 88 | but(self, '2', 7, 2, lambda c='2': self.keyin(c), clr='steelblue') 89 | but(self, '3', 7, 4, lambda c='3': self.keyin(c), clr='steelblue') 90 | but(self, '*', 7, 6, lambda op='*': self.calc(op)) 91 | but(self, 'Clx', 7, 8, self.clearx, clr='darkred') 92 | but(self, 'Clr', 7, 10, self.clearall, clr='darkred') 93 | but(self, '0', 8, 0, lambda c='0': self.keyin(c), clr='steelblue', pad=3) 94 | but(self, '.', 8, 2, lambda c='.': self.keyin(c)) 95 | but(self, '+/-', 8, 4, lambda op='+/-': self.calc(op)) 96 | but(self, ' / ', 8, 6, lambda c='/': self.calc(c), pad=3) 97 | but(self, 'ENTER', 8, 8, self.enter, span=4, clr='darkgoldenrod') 98 | but(self, 'Sin', 9, 0, lambda op='math.sin(x)': self.func(op, in_cnvrt=1), 99 | span=3, clr='darkgoldenrod') 100 | but(self, 'Cos', 9, 3, lambda op='math.cos(x)': self.func(op, in_cnvrt=1), 101 | span=3, clr='darkgoldenrod') 102 | but(self, 'Tan', 9, 6, lambda op='math.tan(x)': self.func(op, in_cnvrt=1), 103 | span=3, clr='darkgoldenrod') 104 | but(self, 'Pi', 9, 9, lambda op='math.pi': self.func(op), span=3, clr='darkgoldenrod') 105 | but(self, 'ASin', 10, 0, lambda op='math.asin(x)': self.func(op, out_cnvrt=1), 106 | span=3, clr='darkgoldenrod') 107 | but(self, 'ACos', 10, 3, lambda op='math.acos(x)': self.func(op, out_cnvrt=1), 108 | span=3, clr='darkgoldenrod') 109 | but(self, 'ATan', 10, 6, lambda op='math.atan(x)': self.func(op, out_cnvrt=1), 110 | span=3, clr='darkgoldenrod') 111 | but(self, '', 10, 9, span=3, clr='darkgoldenrod') 112 | but(self, 'x^2', 11, 0, lambda op='x**2': self.func(op), span=3, clr='darkgreen') 113 | but(self, '1/x', 11, 3, lambda op='1/x': self.func(op), span=3, clr='darkgreen') 114 | but(self, 'e^x', 11, 6, lambda op='math.e**x': self.func(op), span=3, clr='darkgreen') 115 | but(self, '10^x', 11, 9, lambda op='10**x': self.func(op), span=3, clr='darkgreen') 116 | but(self, 'Sqrt', 12, 0, lambda op='math.sqrt(x)': self.func(op), span=3, clr='darkgreen') 117 | but(self, 'y^x', 12, 3, lambda op='y**x': self.func(op), span=3, clr='darkgreen') 118 | but(self, 'ln', 12, 6, lambda op='math.log(x)': self.func(op), span=3, clr='darkgreen') 119 | but(self, 'log', 12, 9, lambda op='math.log10(x)': self.func(op), span=3, clr='darkgreen') 120 | 121 | 122 | def quit(self): 123 | if self.caller: 124 | self.caller.calculator = None 125 | self.destroy() 126 | 127 | def pr(self, val): 128 | """Send value in register to caller.""" 129 | # There must be a better way to get this value 130 | str_value = `eval('self.'+val+'display.get()')`.strip("'") 131 | self.caller.enterfloat(str_value) 132 | self.keip = False 133 | self.needrup = True 134 | 135 | def keyin(self, c): 136 | if self.keip: 137 | self.xdisplay.set(self.xdisplay.get()+c) 138 | else: 139 | self.keip = True 140 | if self.needrup: 141 | self.rotateup(loop=0) 142 | self.clearx() 143 | self.keyin(c) 144 | 145 | def enter(self): 146 | self.tdisplay.set(self.zdisplay.get()) 147 | self.zdisplay.set(self.ydisplay.get()) 148 | self.ydisplay.set(self.xdisplay.get()) 149 | self.keip = False 150 | self.needrup = False 151 | 152 | def calc(self, op): 153 | """Arithmetic calculations between x and y registers, then rotate down.""" 154 | try: 155 | if op == '+/-': 156 | self.xdisplay.set(`eval('-'+self.xdisplay.get())`) 157 | else: 158 | x = `eval(self.ydisplay.get()+op+self.xdisplay.get())` 159 | self.xdisplay.set(x) 160 | self.ydisplay.set(self.zdisplay.get()) 161 | self.zdisplay.set(self.tdisplay.get()) 162 | self.keip = False 163 | self.needrup = True 164 | except: 165 | self.xdisplay.set("ERROR") 166 | 167 | 168 | def func(self, op, in_cnvrt=0, out_cnvrt=0): 169 | """Evaluate function op then put result in x-register, don't rotate stack. 170 | if in_cnvrt: convert input value of x-register from degrees to radians. 171 | if out_cnvrt: convert output value of x-register from radians to degrees.""" 172 | try: 173 | x = float(self.xdisplay.get()) 174 | except: 175 | x = 0 176 | try: 177 | y = float(self.ydisplay.get()) 178 | except: 179 | y = 0 180 | if in_cnvrt: 181 | x = x * math.pi / 180 182 | result = eval(op) 183 | if out_cnvrt: 184 | result = result * 180 / math.pi 185 | self.xdisplay.set(f2s(result)) 186 | self.keip = False 187 | self.needrup = True 188 | 189 | def mm2in(self): 190 | if self.xdisplay.get(): 191 | self.xdisplay.set(`eval(self.xdisplay.get()+'/25.4')`) 192 | self.keip = False 193 | self.needrup = True 194 | 195 | def in2mm(self): 196 | if self.xdisplay.get(): 197 | self.xdisplay.set(`eval(self.xdisplay.get()+'*25.4')`) 198 | self.keip = False 199 | self.needrup = True 200 | 201 | def storex(self): 202 | self.mem = self.xdisplay.get() 203 | self.keip = False 204 | self.needrup = True 205 | 206 | def recallx(self): 207 | self.rotateup() 208 | self.xdisplay.set(self.mem) 209 | self.keip = False 210 | self.needrup = True 211 | 212 | def rotateup(self, loop=1): 213 | x = self.tdisplay.get() 214 | self.tdisplay.set(self.zdisplay.get()) 215 | self.zdisplay.set(self.ydisplay.get()) 216 | self.ydisplay.set(self.xdisplay.get()) 217 | if loop: 218 | self.xdisplay.set(x) 219 | 220 | def rotatedn(self): 221 | x = self.xdisplay.get() 222 | self.xdisplay.set(self.ydisplay.get()) 223 | self.ydisplay.set(self.zdisplay.get()) 224 | self.zdisplay.set(self.tdisplay.get()) 225 | self.tdisplay.set(x) 226 | 227 | def trimx(self): 228 | self.xdisplay.set(self.xdisplay.get()[:-1]) 229 | 230 | def swapxy(self): 231 | x = self.xdisplay.get() 232 | y = self.ydisplay.get() 233 | self.xdisplay.set(y) 234 | self.ydisplay.set(x) 235 | 236 | def clearx(self): 237 | self.xdisplay.set('') 238 | 239 | def clearall(self): 240 | self.xdisplay.set('') 241 | self.ydisplay.set('') 242 | self.zdisplay.set('') 243 | self.tdisplay.set('') 244 | 245 | def putx(self, value): 246 | if self.needrup: 247 | self.rotateup(loop=0) 248 | self.xdisplay.set(`value`) 249 | self.keip = False 250 | self.needrup = True 251 | 252 | if __name__ == '__main__': 253 | Calculator().mainloop() 254 | -------------------------------------------------------------------------------- /myDisplay/Readme.txt: -------------------------------------------------------------------------------- 1 | This folder is a copy of "site-packages/OCC/Display". 2 | Having this local copy facilitates changing the navigation controls. 3 | Mouse button navigation controls are defined in the file qtDisplay.py. 4 | 5 | Navigation controls have been modified as follows: 6 | Ctrl LMB Pan 7 | Ctrl MMB Rotate 8 | Ctrl RMB Zoom 9 | 10 | Using the Ctrl key as a modifier is intended to make it less likely to 11 | accidentally make a screen selection while navigating. 12 | This should reduce the problem of unwanted screen picks being sent to 13 | the registered callback function. 14 | -------------------------------------------------------------------------------- /myDisplay/SimpleGui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2009-2016 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 logging 21 | import os 22 | import sys 23 | 24 | from OCC import VERSION 25 | from OCC.Display.backend import load_backend, get_qt_modules 26 | from OCC.Display.OCCViewer import OffscreenRenderer 27 | 28 | log = logging.getLogger(__name__) 29 | 30 | 31 | def check_callable(_callable): 32 | if not callable(_callable): 33 | raise AssertionError("The function supplied is not callable") 34 | 35 | 36 | def init_display(backend_str=None, 37 | size=(1024, 768), 38 | display_triedron=True, 39 | background_gradient_color1=[206, 215, 222], 40 | background_gradient_color2=[128, 128, 128]): 41 | """ This function loads and initialize a GUI using either wx, pyq4, pyqt5 or pyside. 42 | If ever the environment variable PYTHONOCC_OFFSCREEN_RENDERER, then the GUI is simply 43 | ignored and an offscreen renderer is returned. 44 | init_display returns 4 objects : 45 | * display : an instance of Viewer3d ; 46 | * start_display : a function (the GUI mainloop) ; 47 | * add_menu : a function that creates a menu in the GUI 48 | * add_function_to_menu : adds a menu option 49 | 50 | In case an offscreen renderer is returned, start_display and add_menu are ignored, i.e. 51 | an empty function is returned (named do_nothing). add_function_to_menu just execute the 52 | function taken as a paramter. 53 | 54 | Note : the offscreen renderer is used on the travis side. 55 | """ 56 | if os.getenv("PYTHONOCC_OFFSCREEN_RENDERER") == "1": 57 | # create the offscreen renderer 58 | offscreen_renderer = OffscreenRenderer() 59 | 60 | def do_nothing(*kargs, **kwargs): 61 | """ takes as many parameters as you want, 62 | ans does nothing 63 | """ 64 | pass 65 | 66 | def call_function(s, func): 67 | """ A function that calls another function. 68 | Helpfull to bypass add_function_to_menu. s should be a string 69 | """ 70 | check_callable(func) 71 | log.info("Execute %s :: %s menu fonction" % (s, func.__name__)) 72 | func() 73 | log.info("done") 74 | 75 | # returns empty classes and functions 76 | return offscreen_renderer, do_nothing, do_nothing, call_function 77 | used_backend = load_backend(backend_str) 78 | log.info("GUI backend set to: %s", used_backend) 79 | # wxPython based simple GUI 80 | if used_backend == 'wx': 81 | import wx 82 | from OCC.Display.wxDisplay import wxViewer3d 83 | 84 | class AppFrame(wx.Frame): 85 | 86 | def __init__(self, parent): 87 | wx.Frame.__init__(self, parent, -1, "pythonOCC-%s 3d viewer ('wx' backend)" % VERSION, 88 | style=wx.DEFAULT_FRAME_STYLE, size=size) 89 | self.canva = wxViewer3d(self) 90 | self.menuBar = wx.MenuBar() 91 | self._menus = {} 92 | self._menu_methods = {} 93 | 94 | def add_menu(self, menu_name): 95 | _menu = wx.Menu() 96 | self.menuBar.Append(_menu, "&" + menu_name) 97 | self.SetMenuBar(self.menuBar) 98 | self._menus[menu_name] = _menu 99 | 100 | def add_function_to_menu(self, menu_name, _callable): 101 | # point on curve 102 | _id = wx.NewId() 103 | check_callable(_callable) 104 | try: 105 | self._menus[menu_name].Append(_id, 106 | _callable.__name__.replace('_', ' ').lower()) 107 | except KeyError: 108 | raise ValueError('the menu item %s does not exist' % menu_name) 109 | self.Bind(wx.EVT_MENU, _callable, id=_id) 110 | 111 | app = wx.App(False) 112 | win = AppFrame(None) 113 | win.Show(True) 114 | wx.SafeYield() 115 | win.canva.InitDriver() 116 | app.SetTopWindow(win) 117 | display = win.canva._display 118 | 119 | def add_menu(*args, **kwargs): 120 | win.add_menu(*args, **kwargs) 121 | 122 | def add_function_to_menu(*args, **kwargs): 123 | win.add_function_to_menu(*args, **kwargs) 124 | 125 | def start_display(): 126 | app.MainLoop() 127 | 128 | # Qt based simple GUI 129 | elif 'qt' in used_backend: 130 | from OCC.Display.qtDisplay import qtViewer3d 131 | QtCore, QtGui, QtWidgets, QtOpenGL = get_qt_modules() 132 | 133 | class MainWindow(QtWidgets.QMainWindow): 134 | 135 | def __init__(self, *args): 136 | QtWidgets.QMainWindow.__init__(self, *args) 137 | self.canva = qtViewer3d(self) 138 | self.setWindowTitle("pythonOCC-%s 3d viewer ('%s' backend)" % (VERSION, used_backend)) 139 | self.setCentralWidget(self.canva) 140 | if sys.platform != 'darwin': 141 | self.menu_bar = self.menuBar() 142 | else: 143 | # create a parentless menubar 144 | # see: http://stackoverflow.com/questions/11375176/qmenubar-and-qmenu-doesnt-show-in-mac-os-x?lq=1 145 | # noticeable is that the menu ( alas ) is created in the 146 | # topleft of the screen, just 147 | # next to the apple icon 148 | # still does ugly things like showing the "Python" menu in 149 | # bold 150 | self.menu_bar = QtWidgets.QMenuBar() 151 | self._menus = {} 152 | self._menu_methods = {} 153 | # place the window in the center of the screen, at half the 154 | # screen size 155 | self.centerOnScreen() 156 | 157 | def centerOnScreen(self): 158 | '''Centers the window on the screen.''' 159 | resolution = QtWidgets.QApplication.desktop().screenGeometry() 160 | x = (resolution.width() - self.frameSize().width()) / 2 161 | y = (resolution.height() - self.frameSize().height()) / 2 162 | self.move(x, y) 163 | 164 | def add_menu(self, menu_name): 165 | _menu = self.menu_bar.addMenu("&" + menu_name) 166 | self._menus[menu_name] = _menu 167 | 168 | def add_function_to_menu(self, menu_name, _callable): 169 | check_callable(_callable) 170 | try: 171 | _action = QtWidgets.QAction(_callable.__name__.replace('_', ' ').lower(), self) 172 | # if not, the "exit" action is now shown... 173 | _action.setMenuRole(QtWidgets.QAction.NoRole) 174 | _action.triggered.connect(_callable) 175 | 176 | self._menus[menu_name].addAction(_action) 177 | except KeyError: 178 | raise ValueError('the menu item %s does not exist' % menu_name) 179 | 180 | # following couple of lines is a tweak to enable ipython --gui='qt' 181 | app = QtWidgets.QApplication.instance() # checks if QApplication already exists 182 | if not app: # create QApplication if it doesnt exist 183 | app = QtWidgets.QApplication(sys.argv) 184 | win = MainWindow() 185 | win.resize(size[0] -1, size[1] -1) 186 | win.show() 187 | win.centerOnScreen() 188 | win.canva.InitDriver() 189 | win.resize(size[0], size[1]) 190 | win.canva.qApp = app 191 | display = win.canva._display 192 | 193 | def add_menu(*args, **kwargs): 194 | win.add_menu(*args, **kwargs) 195 | 196 | def add_function_to_menu(*args, **kwargs): 197 | win.add_function_to_menu(*args, **kwargs) 198 | 199 | def start_display(): 200 | win.raise_() # make the application float to the top 201 | app.exec_() 202 | 203 | if display_triedron: 204 | display.display_triedron() 205 | 206 | if background_gradient_color1 and background_gradient_color2: 207 | # background gradient 208 | display.set_bg_gradient_color(background_gradient_color1, background_gradient_color2) 209 | 210 | return display, start_display, add_menu, add_function_to_menu 211 | 212 | 213 | if __name__ == '__main__': 214 | display, start_display, add_menu, add_function_to_menu = init_display("qt-pyqt5") 215 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere, BRepPrimAPI_MakeBox 216 | 217 | def sphere(event=None): 218 | display.DisplayShape(BRepPrimAPI_MakeSphere(100).Shape(), update=True) 219 | 220 | def cube(event=None): 221 | display.DisplayShape(BRepPrimAPI_MakeBox(1, 1, 1).Shape(), update=True) 222 | 223 | def quit(event=None): 224 | sys.exit() 225 | 226 | add_menu('primitives') 227 | add_function_to_menu('primitives', sphere) 228 | add_function_to_menu('primitives', cube) 229 | add_function_to_menu('primitives', quit) 230 | start_display() 231 | -------------------------------------------------------------------------------- /myDisplay/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/myDisplay/__init__.py -------------------------------------------------------------------------------- /myDisplay/backend.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | # backend constants 5 | WX = "wx" 6 | PYSIDE = "qt-pyside" 7 | PYQT4 = "qt-pyqt4" 8 | PYQT5 = "qt-pyqt5" 9 | 10 | # backend module 11 | HAVE_PYQT5, HAVE_PYQT4, HAVE_PYSIDE, HAVE_WX = False, False, False, False 12 | 13 | # is any backend imported? 14 | HAVE_BACKEND = False 15 | BACKEND_MODULE = "No backend loaded" 16 | 17 | logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | def load_pyqt5(): 22 | """ returns True is PyQt5 found, else False 23 | """ 24 | global HAVE_PYQT5, QtCore, QtGui, QtWidgets, QtOpenGL 25 | 26 | # backend already loaded, dont load another one 27 | if loaded_backend(): 28 | return False 29 | 30 | try: 31 | from PyQt5 import QtCore, QtGui, QtOpenGL, QtWidgets 32 | HAVE_PYQT5 = True 33 | except ImportError: 34 | HAVE_PYQT5 = False 35 | return HAVE_PYQT5 36 | 37 | 38 | def load_pyqt4(): 39 | """ returns True is PyQt4 found, else False 40 | """ 41 | global HAVE_PYQT4, QtCore, QtGui, QtWidgets, QtOpenGL 42 | 43 | # backend already loaded, dont load another one 44 | if loaded_backend(): 45 | return False 46 | 47 | try: 48 | from PyQt4 import QtCore, QtGui, QtOpenGL 49 | QtWidgets = QtGui 50 | HAVE_PYQT4 = True 51 | except ImportError: 52 | HAVE_PYQT4 = False 53 | return HAVE_PYQT4 54 | 55 | 56 | def load_pyside(): 57 | """ returns True is PySide found, else False 58 | """ 59 | global HAVE_PYSIDE, QtCore, QtGui, QtWidgets, QtOpenGL 60 | 61 | # backend already loaded, dont load another one 62 | if loaded_backend(): 63 | return False 64 | 65 | try: 66 | from PySide import QtCore, QtGui, QtOpenGL 67 | QtWidgets = QtGui 68 | HAVE_PYSIDE = True 69 | except ImportError: 70 | HAVE_PYSIDE = False 71 | return HAVE_PYSIDE 72 | 73 | 74 | def load_wx(): 75 | """ returns True is wxPython found, else False 76 | """ 77 | 78 | # backend already loaded, dont load another one 79 | if loaded_backend(): 80 | return False 81 | 82 | global HAVE_WX 83 | try: 84 | import wx 85 | HAVE_WX = True 86 | except ImportError: 87 | HAVE_WX = False 88 | return HAVE_WX 89 | 90 | 91 | def loaded_backend(): 92 | return HAVE_BACKEND 93 | 94 | 95 | def get_loaded_backend(): 96 | return BACKEND_MODULE 97 | 98 | 99 | def load_any_qt_backend(): 100 | """ loads any qt based backend. First try to load 101 | PyQt5, then PyQt4 and finally PySide. Raise an exception 102 | if none of them are available 103 | """ 104 | pyqt5_loaded = False 105 | pyqt4_loaded = False 106 | pyside_loaded = False 107 | # by default, load PyQt5 108 | pyqt5_loaded = load_backend(PYQT5) 109 | if not pyqt5_loaded: 110 | # load pyqt4 111 | pyqt4_loaded = load_backend(PYQT4) 112 | # finally try to load pyside 113 | if not pyqt4_loaded: 114 | pyside_loaded = load_backend(PYSIDE) 115 | if not (pyqt5_loaded or pyqt4_loaded or pyside_loaded): 116 | raise AssertionError("None of the PyQt5 orPtQt4 or PySide backend can be loaded") 117 | else: 118 | return True 119 | 120 | 121 | def load_backend(backend_str=None): 122 | """ loads a gui backend 123 | 124 | If no Qt (such as PyQt5, PyQt4 or PySide) backend is found, wx is loaded 125 | 126 | The search order for pythonocc compatible gui modules is: 127 | PyQt5, PyQt4, PySide, Wx 128 | 129 | Note 130 | ---- 131 | Wx is imported when no Qt backend is found. 132 | 133 | Parameters 134 | ---------- 135 | backend_str : str 136 | 137 | specifies which backend to load 138 | 139 | backend_str is one of ( "qt-pyqt5", "qt-pyqt4", "qt-pyside", "wx" ) 140 | 141 | if no value has been set, load the first module in gui module search 142 | order 143 | 144 | Returns 145 | ------- 146 | str 147 | the name of the loaded backend 148 | one of ( "qt-pyqt5", "qt-pyqt4", "qt-pyside", "wx" ) 149 | 150 | Raises 151 | ------ 152 | 153 | ValueError 154 | * when a backend is already loaded 155 | * when an invalid backend_str is specified 156 | 157 | ImportError 158 | when the backend specified in ``backend_str`` could not be imported 159 | 160 | """ 161 | global HAVE_BACKEND, BACKEND_MODULE 162 | 163 | if HAVE_BACKEND: 164 | msg = "The {0} backend is already loaded..." \ 165 | "``load_backend`` can only be called once per session".format(BACKEND_MODULE) 166 | log.info(msg) 167 | return BACKEND_MODULE 168 | 169 | if backend_str is not None: 170 | compatible_backends = (PYQT5, PYQT4, PYSIDE, WX) 171 | if not backend_str in compatible_backends: 172 | msg = "incompatible backend_str specified: {0}\n" \ 173 | "backend is one of : {1}".format(backend_str, 174 | compatible_backends) 175 | log.critical(msg) 176 | raise ValueError(msg) 177 | 178 | if backend_str == PYQT5 or backend_str is None: 179 | if load_pyqt5(): 180 | HAVE_BACKEND = True 181 | BACKEND_MODULE = 'qt-pyqt5' 182 | log.info("backend loaded: {0}".format(BACKEND_MODULE)) 183 | return BACKEND_MODULE 184 | if backend_str == PYQT5 and not HAVE_BACKEND: 185 | msg = "{0} backend could not be loaded".format(backend_str) 186 | log.exception(msg) 187 | raise ValueError(msg) 188 | else: 189 | pass 190 | 191 | if backend_str == PYQT4 or (backend_str is None and not HAVE_BACKEND): 192 | if load_pyqt4(): 193 | HAVE_BACKEND = True 194 | BACKEND_MODULE = 'qt-pyqt4' 195 | log.info("backend loaded: {0}".format(BACKEND_MODULE)) 196 | return BACKEND_MODULE 197 | elif backend_str == PYQT4 and not HAVE_BACKEND: 198 | msg = "{0} backend could not be loaded".format(backend_str) 199 | log.exception(msg) 200 | raise ValueError(msg) 201 | 202 | else: 203 | pass 204 | 205 | if backend_str == PYSIDE or (backend_str is None and not HAVE_BACKEND): 206 | if load_pyside(): 207 | HAVE_BACKEND = True 208 | BACKEND_MODULE = 'qt-pyside' 209 | log.info("backend loaded: {0}".format(BACKEND_MODULE)) 210 | return BACKEND_MODULE 211 | elif backend_str == PYSIDE and not HAVE_BACKEND: 212 | msg = "{0} backend could not be loaded".format(backend_str) 213 | log.exception(msg) 214 | raise ValueError(msg) 215 | else: 216 | pass 217 | 218 | if backend_str == WX or (backend_str is None and not HAVE_BACKEND): 219 | if load_wx(): 220 | HAVE_BACKEND = True 221 | BACKEND_MODULE = 'wx' 222 | log.info("backend loaded: {0}".format(BACKEND_MODULE)) 223 | return BACKEND_MODULE 224 | elif backend_str == WX and not HAVE_BACKEND: 225 | msg = "{0} backend could not be loaded".format(backend_str) 226 | log.exception(msg) 227 | raise ValueError(msg) 228 | else: 229 | pass 230 | 231 | if not HAVE_BACKEND: 232 | raise ImportError("No compliant GUI library could be imported.\n" 233 | "Either PyQt5, PyQt4, PySide, or wxPython " 234 | "is required") 235 | 236 | 237 | def get_qt_modules(): 238 | """ 239 | 240 | Returns 241 | ------- 242 | tuple : ( QtCore, QtGui, QtWidgets, QtOpenGL ) 243 | here QtWidgets shadows QtGui when a PyQt4 or PySide module is loaded 244 | this is the most coherent way to get PyQt5 compliant code 245 | 246 | Raises 247 | ------ 248 | 249 | ValueError 250 | when no Qt backend has been yet loaded 251 | informs the user to call `load_backend` or that no Qt python module 252 | ( PyQt5, PyQt4 or PySide ) is found 253 | 254 | """ 255 | if not HAVE_BACKEND: 256 | raise ValueError("no backend has been imported yet with " 257 | "``load_backend``... ") 258 | 259 | if HAVE_PYQT5 or HAVE_PYQT4 or HAVE_PYSIDE: 260 | return QtCore, QtGui, QtWidgets, QtOpenGL 261 | elif HAVE_WX: 262 | raise ValueError("the Wx backend is already loaded") 263 | else: 264 | msg = ("no Qt backend is loaded, hence cannot return any modules\n" 265 | "either you havent got PyQt5, PyQt4 or PySide installed\n" 266 | "or you havent yet loaded a backend with the " 267 | "`OCC.Display.backend.load_backend` function") 268 | raise ValueError(msg) 269 | -------------------------------------------------------------------------------- /myDisplay/icons/cursor-magnify-area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/myDisplay/icons/cursor-magnify-area.png -------------------------------------------------------------------------------- /myDisplay/icons/cursor-magnify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/myDisplay/icons/cursor-magnify.png -------------------------------------------------------------------------------- /myDisplay/icons/cursor-pan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/myDisplay/icons/cursor-pan.png -------------------------------------------------------------------------------- /myDisplay/icons/cursor-rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblanding/cadviewer/690c7609c672b7ecb89a8914230c6273d3702326/myDisplay/icons/cursor-rotate.png -------------------------------------------------------------------------------- /myDisplay/qtDisplay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ##Copyright 2009-2019 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 | from __future__ import print_function 21 | 22 | import logging 23 | import os 24 | import sys 25 | 26 | from OCC.Display import OCCViewer 27 | from OCC.Display.backend import get_qt_modules 28 | 29 | QtCore, QtGui, QtWidgets, QtOpenGL = get_qt_modules() 30 | 31 | # check if signal available, not available 32 | # on PySide 33 | HAVE_PYQT_SIGNAL = hasattr(QtCore, 'pyqtSignal') 34 | 35 | logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 36 | log = logging.getLogger(__name__) 37 | 38 | 39 | class qtBaseViewer(QtOpenGL.QGLWidget): 40 | ''' The base Qt Widget for an OCC viewer 41 | ''' 42 | def __init__(self, parent=None): 43 | super(qtBaseViewer, self).__init__(parent) 44 | self._display = None 45 | self._inited = False 46 | 47 | # enable Mouse Tracking 48 | self.setMouseTracking(True) 49 | 50 | # Strong focus 51 | self.setFocusPolicy(QtCore.Qt.WheelFocus) 52 | 53 | # required for overpainting the widget 54 | self.setAttribute(QtCore.Qt.WA_PaintOnScreen) 55 | self.setAttribute(QtCore.Qt.WA_NoSystemBackground) 56 | 57 | self.setAutoFillBackground(False) 58 | 59 | def GetHandle(self): 60 | ''' returns an the identifier of the GUI widget. 61 | It must be an integer 62 | ''' 63 | win_id = self.winId() # this returns either an int or voitptr 64 | if "%s" % type(win_id) == "": # PySide 65 | ### with PySide, self.winId() does not return an integer 66 | if sys.platform == "win32": 67 | ## Be careful, this hack is py27 specific 68 | ## does not work with python31 or higher 69 | ## since the PyCObject api was changed 70 | import ctypes 71 | ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ctypes.c_void_p 72 | ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = [ctypes.py_object] 73 | win_id = ctypes.pythonapi.PyCObject_AsVoidPtr(win_id) 74 | elif not isinstance(win_id, int): # PyQt4 or 5 75 | ## below integer cast may be required because self.winId() can 76 | ## returns a sip.voitptr according to the PyQt version used 77 | ## as well as the python version 78 | win_id = int(win_id) 79 | return win_id 80 | 81 | def resizeEvent(self, event): 82 | if self._inited: 83 | super(qtBaseViewer, self).resizeEvent(event) 84 | self._display.OnResize() 85 | 86 | 87 | class qtViewer3d(qtBaseViewer): 88 | 89 | # emit signal when selection is changed 90 | # is a list of TopoDS_* 91 | if HAVE_PYQT_SIGNAL: 92 | sig_topods_selected = QtCore.pyqtSignal(list) 93 | 94 | def __init__(self, *kargs): 95 | qtBaseViewer.__init__(self, *kargs) 96 | 97 | self.setObjectName("qt_viewer_3d") 98 | 99 | self._drawbox = False 100 | self._zoom_area = False 101 | self._select_area = False 102 | self._inited = False 103 | self._leftisdown = False 104 | self._middleisdown = False 105 | self._rightisdown = False 106 | self._selection = None 107 | self._drawtext = True 108 | self._qApp = QtWidgets.QApplication.instance() 109 | self._key_map = {} 110 | self._current_cursor = "arrow" 111 | self._available_cursors = {} 112 | 113 | @property 114 | def qApp(self): 115 | # reference to QApplication instance 116 | return self._qApp 117 | 118 | @qApp.setter 119 | def qApp(self, value): 120 | self._qApp = value 121 | 122 | def InitDriver(self): 123 | self._display = OCCViewer.Viewer3d(window_handle=self.GetHandle(), parent=self) 124 | self._display.Create() 125 | # background gradient 126 | self._display.SetModeShaded() 127 | self._inited = True 128 | # dict mapping keys to functions 129 | self._key_map = {ord('W'): self._display.SetModeWireFrame, 130 | ord('S'): self._display.SetModeShaded, 131 | ord('A'): self._display.EnableAntiAliasing, 132 | ord('B'): self._display.DisableAntiAliasing, 133 | ord('H'): self._display.SetModeHLR, 134 | ord('F'): self._display.FitAll, 135 | ord('G'): self._display.SetSelectionMode} 136 | self.createCursors() 137 | 138 | def createCursors(self): 139 | module_pth = os.path.abspath(os.path.dirname(__file__)) 140 | icon_pth = os.path.join(module_pth, "icons") 141 | 142 | _CURSOR_PIX_ROT = QtGui.QPixmap(os.path.join(icon_pth, "cursor-rotate.png")) 143 | _CURSOR_PIX_PAN = QtGui.QPixmap(os.path.join(icon_pth, "cursor-pan.png")) 144 | _CURSOR_PIX_ZOOM = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify.png")) 145 | _CURSOR_PIX_ZOOM_AREA = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify-area.png")) 146 | 147 | self._available_cursors = { 148 | "arrow": QtGui.QCursor(QtCore.Qt.ArrowCursor), # default 149 | "pan": QtGui.QCursor(_CURSOR_PIX_PAN), 150 | "rotate": QtGui.QCursor(_CURSOR_PIX_ROT), 151 | "zoom": QtGui.QCursor(_CURSOR_PIX_ZOOM), 152 | "zoom-area": QtGui.QCursor(_CURSOR_PIX_ZOOM_AREA), 153 | } 154 | 155 | self._current_cursor = "arrow" 156 | 157 | def keyPressEvent(self, event): # original 158 | code = event.key() 159 | if code in self._key_map: 160 | self._key_map[code]() 161 | elif code in range(256): 162 | log.info('key: "%s"(code %i) not mapped to any function' % (chr(code), code)) 163 | else: 164 | log.info('key: code %i not mapped to any function' % code) 165 | 166 | def keyPressEvent(self, event): # modified 167 | code = event.key() 168 | if code in self._key_map: 169 | self._key_map[code]() 170 | elif code in range(256): 171 | log.info('key: "%s"(code %i) not mapped to any function' % (chr(code), code)) 172 | else: 173 | pass # Keep quiet about pressing 'ctrl' key 174 | 175 | def focusInEvent(self, event): 176 | if self._inited: 177 | self._display.Repaint() 178 | 179 | def focusOutEvent(self, event): 180 | if self._inited: 181 | self._display.Repaint() 182 | 183 | def paintEvent(self, event): 184 | if self._drawbox: 185 | self._display.Repaint() 186 | self._display.Repaint() 187 | painter = QtGui.QPainter(self) 188 | painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 2)) 189 | rect = QtCore.QRect(*self._drawbox) 190 | painter.drawRect(rect) 191 | 192 | def wheelEvent(self, event): 193 | try: # PyQt4/PySide 194 | delta = event.delta() 195 | except: # PyQt5 196 | delta = event.angleDelta().y() 197 | if delta > 0: 198 | zoom_factor = 2. 199 | else: 200 | zoom_factor = 0.5 201 | self._display.ZoomFactor(zoom_factor) 202 | 203 | @property 204 | def cursor(self): 205 | return self._current_cursor 206 | 207 | @cursor.setter 208 | def cursor(self, value): 209 | if not self._current_cursor == value: 210 | 211 | self._current_cursor = value 212 | cursor = self._available_cursors.get(value) 213 | 214 | if cursor: 215 | self.qApp.setOverrideCursor(cursor) 216 | else: 217 | self.qApp.restoreOverrideCursor() 218 | 219 | def mousePressEvent(self, event): 220 | self.setFocus() 221 | ev = event.pos() 222 | self.dragStartPosX = ev.x() 223 | self.dragStartPosY = ev.y() 224 | self._display.StartRotation(self.dragStartPosX, self.dragStartPosY) 225 | 226 | def mouseReleaseEvent(self, event): 227 | pt = event.pos() 228 | modifiers = event.modifiers() 229 | 230 | if event.button() == QtCore.Qt.LeftButton: 231 | if self._select_area: 232 | [Xmin, Ymin, dx, dy] = self._drawbox 233 | self._display.SelectArea(Xmin, Ymin, Xmin + dx, Ymin + dy) 234 | self._select_area = False 235 | else: 236 | # multiple select if shift is pressed 237 | if modifiers == QtCore.Qt.ShiftModifier: 238 | self._display.ShiftSelect(pt.x(), pt.y()) 239 | else: 240 | # single select otherwise 241 | self._display.Select(pt.x(), pt.y()) 242 | 243 | if (self._display.selected_shapes is not None) and HAVE_PYQT_SIGNAL: 244 | self.sig_topods_selected.emit(self._display.selected_shapes) 245 | 246 | 247 | elif event.button() == QtCore.Qt.RightButton: 248 | if self._zoom_area: 249 | [Xmin, Ymin, dx, dy] = self._drawbox 250 | self._display.ZoomArea(Xmin, Ymin, Xmin + dx, Ymin + dy) 251 | self._zoom_area = False 252 | 253 | self.cursor = "arrow" 254 | 255 | def DrawBox(self, event): 256 | tolerance = 2 257 | pt = event.pos() 258 | dx = pt.x() - self.dragStartPosX 259 | dy = pt.y() - self.dragStartPosY 260 | if abs(dx) <= tolerance and abs(dy) <= tolerance: 261 | return 262 | self._drawbox = [self.dragStartPosX, self.dragStartPosY, dx, dy] 263 | 264 | 265 | def mouseMoveEvent(self, evt): # Original version 266 | pt = evt.pos() 267 | buttons = int(evt.buttons()) 268 | modifiers = evt.modifiers() 269 | # ROTATE 270 | if (buttons == QtCore.Qt.LeftButton and 271 | not modifiers == QtCore.Qt.ShiftModifier): 272 | self.cursor = "rotate" 273 | self._display.Rotation(pt.x(), pt.y()) 274 | self._drawbox = False 275 | # DYNAMIC ZOOM 276 | elif (buttons == QtCore.Qt.RightButton and 277 | not modifiers == QtCore.Qt.ShiftModifier): 278 | self.cursor = "zoom" 279 | self._display.Repaint() 280 | self._display.DynamicZoom(abs(self.dragStartPosX), 281 | abs(self.dragStartPosY), abs(pt.x()), 282 | abs(pt.y())) 283 | self.dragStartPosX = pt.x() 284 | self.dragStartPosY = pt.y() 285 | self._drawbox = False 286 | # PAN 287 | elif buttons == QtCore.Qt.MidButton: 288 | dx = pt.x() - self.dragStartPosX 289 | dy = pt.y() - self.dragStartPosY 290 | self.dragStartPosX = pt.x() 291 | self.dragStartPosY = pt.y() 292 | self.cursor = "pan" 293 | self._display.Pan(dx, -dy) 294 | self._drawbox = False 295 | # DRAW BOX 296 | # ZOOM WINDOW 297 | elif (buttons == QtCore.Qt.RightButton and 298 | modifiers == QtCore.Qt.ShiftModifier): 299 | self._zoom_area = True 300 | self.cursor = "zoom-area" 301 | self.DrawBox(evt) 302 | self.update() 303 | # SELECT AREA 304 | elif (buttons == QtCore.Qt.LeftButton and 305 | modifiers == QtCore.Qt.ShiftModifier): 306 | self._select_area = True 307 | self.DrawBox(evt) 308 | self.update() 309 | else: 310 | self._drawbox = False 311 | self._display.MoveTo(pt.x(), pt.y()) 312 | self.cursor = "arrow" 313 | 314 | def mouseMoveEvent(self, evt): # Modified version 315 | pt = evt.pos() 316 | buttons = int(evt.buttons()) 317 | modifiers = evt.modifiers() 318 | # ROTATE 319 | if (buttons == QtCore.Qt.MidButton and 320 | modifiers == QtCore.Qt.ControlModifier): 321 | self.cursor = "rotate" 322 | self._display.Rotation(pt.x(), pt.y()) 323 | self._drawbox = False 324 | # DYNAMIC ZOOM 325 | elif (buttons == QtCore.Qt.RightButton and 326 | modifiers == QtCore.Qt.ControlModifier): 327 | self.cursor = "zoom" 328 | self._display.Repaint() 329 | self._display.DynamicZoom(abs(self.dragStartPosX), 330 | abs(self.dragStartPosY), abs(pt.x()), 331 | abs(pt.y())) 332 | self.dragStartPosX = pt.x() 333 | self.dragStartPosY = pt.y() 334 | self._drawbox = False 335 | # PAN 336 | elif (buttons == QtCore.Qt.LeftButton and 337 | modifiers == QtCore.Qt.ControlModifier): 338 | dx = pt.x() - self.dragStartPosX 339 | dy = pt.y() - self.dragStartPosY 340 | self.dragStartPosX = pt.x() 341 | self.dragStartPosY = pt.y() 342 | self.cursor = "pan" 343 | self._display.Pan(dx, -dy) 344 | self._drawbox = False 345 | # DRAW BOX 346 | # ZOOM WINDOW 347 | elif (buttons == QtCore.Qt.RightButton and 348 | modifiers == QtCore.Qt.ControlModifier): 349 | self._zoom_area = True 350 | self.cursor = "zoom-area" 351 | self.DrawBox(evt) 352 | self.update() 353 | # SELECT AREA 354 | elif (buttons == QtCore.Qt.LeftButton and 355 | modifiers == QtCore.Qt.ControlModifier): 356 | self._select_area = True 357 | self.DrawBox(evt) 358 | self.update() 359 | else: 360 | self._drawbox = False 361 | self._display.MoveTo(pt.x(), pt.y()) 362 | self.cursor = "arrow" 363 | -------------------------------------------------------------------------------- /myDisplay/wxDisplay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ##Copyright 2008-2017 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 | from __future__ import print_function 21 | 22 | import time 23 | 24 | try: 25 | import wx 26 | except ImportError: 27 | raise ImportError('Please install wxPython.') 28 | from OCC.Display import OCCViewer 29 | 30 | 31 | class wxBaseViewer(wx.Panel): 32 | def __init__(self, parent=None): 33 | wx.Panel.__init__(self, parent) 34 | self.Bind(wx.EVT_SIZE, self.OnSize) 35 | self.Bind(wx.EVT_IDLE, self.OnIdle) 36 | self.Bind(wx.EVT_MOVE, self.OnMove) 37 | self.Bind(wx.EVT_SET_FOCUS, self.OnFocus) 38 | self.Bind(wx.EVT_KILL_FOCUS, self.OnLostFocus) 39 | self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize) 40 | self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 41 | self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) 42 | self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) 43 | self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) 44 | self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) 45 | self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) 46 | self.Bind(wx.EVT_MOTION, self.OnMotion) 47 | self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 48 | self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheelScroll) 49 | 50 | self._display = None 51 | self._inited = False 52 | 53 | def GetWinId(self): 54 | """ Returns the windows Id as an integer. 55 | issue with GetHandle on Linux for wx versions 56 | >3 or 4. Window must be displayed before GetHandle is 57 | called. For that, just wait for a few milliseconds/seconds 58 | before calling InitDriver 59 | a solution is given here 60 | see https://github.com/cztomczak/cefpython/issues/349 61 | but raises an issue with wxPython 4.x 62 | finally, it seems that the sleep function does the job 63 | reported as a pythonocc issue 64 | https://github.com/tpaviot/pythonocc-core/476 65 | """ 66 | timeout = 10 # 10 seconds 67 | win_id = self.GetHandle() 68 | init_time = time.time() 69 | delta_t = 0. # elapsed time, initialized to 0 before the while loop 70 | # if ever win_id is 0, enter the loop untill it gets a value 71 | while win_id == 0 and delta_t < timeout: 72 | time.sleep(0.1) 73 | wx.SafeYield() 74 | win_id = self.GetHandle() 75 | delta_t = time.time() - init_time 76 | # check that win_id is different from 0 77 | if win_id == 0: 78 | raise AssertionError("Can't get win Id") 79 | # otherwise returns the window Id 80 | return win_id 81 | 82 | def OnSize(self, event): 83 | if self._inited: 84 | self._display.OnResize() 85 | 86 | def OnIdle(self, event): 87 | pass 88 | 89 | def OnMove(self, event): 90 | pass 91 | 92 | def OnFocus(self, event): 93 | pass 94 | 95 | def OnLostFocus(self, event): 96 | pass 97 | 98 | def OnMaximize(self, event): 99 | pass 100 | 101 | def OnLeftDown(self, event): 102 | pass 103 | 104 | def OnRightDown(self, event): 105 | pass 106 | 107 | def OnMiddleDown(self, event): 108 | pass 109 | 110 | def OnLeftUp(self, event): 111 | pass 112 | 113 | def OnRightUp(self, event): 114 | pass 115 | 116 | def OnMiddleUp(self, event): 117 | pass 118 | 119 | def OnMotion(self, event): 120 | pass 121 | 122 | def OnKeyDown(self, event): 123 | pass 124 | 125 | 126 | class wxViewer3d(wxBaseViewer): 127 | def __init__(self, *kargs): 128 | wxBaseViewer.__init__(self, *kargs) 129 | 130 | self._drawbox = False 131 | self._zoom_area = False 132 | self._select_area = False 133 | self._inited = False 134 | self._leftisdown = False 135 | self._middleisdown = False 136 | self._rightisdown = False 137 | self._selection = None 138 | self._scrollwheel = False 139 | self._key_map = {} 140 | self.dragStartPos = None 141 | 142 | def InitDriver(self): 143 | self._display = OCCViewer.Viewer3d(self.GetWinId()) 144 | self._display.Create() 145 | self._display.SetModeShaded() 146 | self._inited = True 147 | 148 | # dict mapping keys to functions 149 | self._SetupKeyMap() 150 | 151 | def _SetupKeyMap(self): 152 | def set_shade_mode(): 153 | self._display.DisableAntiAliasing() 154 | self._display.SetModeShaded() 155 | self._key_map = {ord('W'): self._display.SetModeWireFrame, 156 | ord('S'): set_shade_mode, 157 | ord('A'): self._display.EnableAntiAliasing, 158 | ord('B'): self._display.DisableAntiAliasing, 159 | ord('H'): self._display.SetModeHLR, 160 | ord('G'): self._display.SetSelectionModeVertex 161 | } 162 | 163 | def OnKeyDown(self, evt): 164 | code = evt.GetKeyCode() 165 | try: 166 | self._key_map[code]() 167 | except KeyError: 168 | print('unrecognized key %i' % evt.GetKeyCode()) 169 | 170 | def OnMaximize(self, event): 171 | if self._inited: 172 | self._display.Repaint() 173 | 174 | def OnMove(self, event): 175 | if self._inited: 176 | self._display.Repaint() 177 | 178 | def OnIdle(self, event): 179 | if self._drawbox: 180 | pass 181 | elif self._inited: 182 | self._display.Repaint() 183 | 184 | def Test(self): 185 | if self._inited: 186 | self._display.Test() 187 | 188 | def OnFocus(self, event): 189 | if self._inited: 190 | self._display.Repaint() 191 | 192 | def OnLostFocus(self, event): 193 | if self._inited: 194 | self._display.Repaint() 195 | 196 | def OnPaint(self, event): 197 | if self._inited: 198 | self._display.Repaint() 199 | 200 | def ZoomAll(self, evt): 201 | self._display.FitAll() 202 | 203 | def Repaint(self, evt): 204 | if self._inited: 205 | self._display.Repaint() 206 | 207 | def OnLeftDown(self, evt): 208 | self.SetFocus() 209 | self.dragStartPos = evt.GetPosition() 210 | self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y) 211 | 212 | def OnLeftUp(self, evt): 213 | pt = evt.GetPosition() 214 | if self._select_area: 215 | [Xmin, Ymin, dx, dy] = self._drawbox 216 | self._display.SelectArea(Xmin, Ymin, Xmin+dx, Ymin+dy) 217 | self._select_area = False 218 | else: 219 | self._display.Select(pt.x, pt.y) 220 | 221 | def OnRightUp(self, evt): 222 | if self._zoom_area: 223 | [Xmin, Ymin, dx, dy] = self._drawbox 224 | self._display.ZoomArea(Xmin, Ymin, Xmin+dx, Ymin+dy) 225 | self._zoom_area = False 226 | 227 | def OnMiddleUp(self, evt): 228 | pass 229 | 230 | def OnRightDown(self, evt): 231 | self.dragStartPos = evt.GetPosition() 232 | self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y) 233 | 234 | def OnMiddleDown(self, evt): 235 | self.dragStartPos = evt.GetPosition() 236 | self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y) 237 | 238 | def OnWheelScroll(self, evt): 239 | # Zooming by wheel 240 | if evt.GetWheelRotation() > 0: 241 | zoom_factor = 2. 242 | else: 243 | zoom_factor = 0.5 244 | self._display.Repaint() 245 | self._display.ZoomFactor(zoom_factor) 246 | 247 | def DrawBox(self, event): 248 | tolerance = 2 249 | pt = event.GetPosition() 250 | dx = pt.x - self.dragStartPos.x 251 | dy = pt.y - self.dragStartPos.y 252 | if abs(dx) <= tolerance and abs(dy) <= tolerance: 253 | return 254 | dc = wx.ClientDC(self) 255 | dc.BeginDrawing() 256 | dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT)) 257 | dc.SetBrush(wx.TRANSPARENT_BRUSH) 258 | dc.SetLogicalFunction(wx.XOR) 259 | if self._drawbox: 260 | r = wx.Rect(*self._drawbox) 261 | dc.DrawRectangleRect(r) 262 | r = wx.Rect(self.dragStartPos.x, self.dragStartPos.y, dx, dy) 263 | dc.DrawRectangleRect(r) 264 | dc.EndDrawing() 265 | self._drawbox = [self.dragStartPos.x, self.dragStartPos.y, dx, dy] 266 | 267 | def OnMotion(self, evt): 268 | pt = evt.GetPosition() 269 | 270 | # ROTATE 271 | if evt.LeftIsDown() and not evt.ShiftDown(): 272 | self._display.Rotation(pt.x, pt.y) 273 | self._drawbox = False 274 | # DYNAMIC ZOOM 275 | elif evt.RightIsDown() and not evt.ShiftDown(): 276 | self._display.Repaint() 277 | self._display.DynamicZoom(abs(self.dragStartPos.x), abs(self.dragStartPos.y), abs(pt.x), abs(pt.y)) 278 | self.dragStartPos.x = pt.x 279 | self.dragStartPos.y = pt.y 280 | self._drawbox = False 281 | # PAN 282 | elif evt.MiddleIsDown(): 283 | dx = pt.x - self.dragStartPos.x 284 | dy = pt.y - self.dragStartPos.y 285 | self.dragStartPos.x = pt.x 286 | self.dragStartPos.y = pt.y 287 | self._display.Pan(dx, -dy) 288 | self._drawbox = False 289 | # DRAW BOX 290 | elif evt.RightIsDown() and evt.ShiftDown(): # ZOOM WINDOW 291 | self._zoom_area = True 292 | self.DrawBox(evt) 293 | elif evt.LeftIsDown() and evt.ShiftDown(): # SELECT AREA 294 | self._select_area = True 295 | self.DrawBox(evt) 296 | else: 297 | self._drawbox = False 298 | self._display.MoveTo(pt.x, pt.y) 299 | 300 | 301 | def TestWxDisplay(): 302 | class AppFrame(wx.Frame): 303 | def __init__(self, parent): 304 | wx.Frame.__init__(self, parent, -1, "wxDisplay3d sample", 305 | style=wx.DEFAULT_FRAME_STYLE, size=(640, 480)) 306 | self.canva = wxViewer3d(self) 307 | 308 | def runTests(self): 309 | self.canva._display.Test() 310 | 311 | app = wx.App(False) 312 | wx.InitAllImageHandlers() 313 | frame = AppFrame(None) 314 | frame.Show(True) 315 | wx.SafeYield() 316 | frame.canva.InitDriver() 317 | frame.runTests() 318 | app.SetTopWindow(frame) 319 | app.MainLoop() 320 | 321 | if __name__ == "__main__": 322 | TestWxDisplay() 323 | -------------------------------------------------------------------------------- /rpnCalculator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2020 Doug Blanding (dblanding@gmail.com) 4 | # 5 | # This file is part of cadViewer. 6 | # The latest version of this file can be found at: 7 | # //https://github.com/dblanding/cadviewer 8 | # 9 | # cadViewer is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # cadViewer is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # if not, write to the Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | 25 | import math 26 | import sys 27 | from PyQt5 import QtCore 28 | from PyQt5.QtWidgets import (QApplication, QToolButton, QDialog, QGridLayout, 29 | QLineEdit, QLayout, QSizePolicy) 30 | 31 | def nyi(): 32 | print('Not yet implemented') 33 | 34 | class Button(QToolButton): 35 | def __init__(self, text, parent=None): 36 | super(Button, self).__init__(parent) 37 | 38 | self.setSizePolicy(QSizePolicy.Minimum, 39 | QSizePolicy.Preferred) 40 | self.setText(text) 41 | 42 | def sizeHint(self): 43 | size = super(Button, self).sizeHint() 44 | size.setHeight(size.height()) 45 | size.setWidth(max(size.width(), size.height())) 46 | return size 47 | 48 | 49 | class Calculator(QDialog): 50 | """RPN calculator styled after the one in CoCreate SolidDesigner CAD.""" 51 | mem = '' 52 | keip = False # Flag set when keyboard entry is in progress 53 | needrup = False # Flag signaling need to rotate up with next keyboard entry 54 | 55 | NumDigitButtons = 10 56 | 57 | def __init__(self, parent=None): 58 | super(Calculator, self).__init__(parent) 59 | self.caller = parent 60 | self.setWindowTitle("RPN Calculator") 61 | 62 | self.x = 0 63 | self.y = 0 64 | self.z = 0 65 | self.t = 0 66 | 67 | self.xdisplay = self.display() 68 | self.ydisplay = self.display() 69 | self.zdisplay = self.display() 70 | self.tdisplay = self.display() 71 | 72 | myblue1 = 'steelblue' 73 | myblue2 = 'darkslateblue' # hsv(240,200,160) 74 | mygray = 'rgb(120,120,120)' # dimgray 75 | mygreen = 'green' 76 | myred = 'hsv(0,255,180)' 77 | mygold = 'goldenrod' 78 | 79 | self.mainLayout = QGridLayout() 80 | self.mainLayout.setSpacing(0) 81 | self.mainLayout.setSizeConstraint(QLayout.SetFixedSize) 82 | 83 | # Grid is 36 columns across 84 | self.butn('T', 0, 0, lambda state, r='t': self.pr(r), colspan=4) 85 | self.butn('Z', 1, 0, lambda state, r='z': self.pr(r), colspan=4) 86 | self.butn('Y', 2, 0, lambda state, r='y': self.pr(r), colspan=4) 87 | self.butn('X', 3, 0, lambda state, r='x': self.pr(r), colspan=4) 88 | self.mainLayout.addWidget(self.tdisplay, 0, 4, 1, 26) 89 | self.mainLayout.addWidget(self.zdisplay, 1, 4, 1, 26) 90 | self.mainLayout.addWidget(self.ydisplay, 2, 4, 1, 26) 91 | self.mainLayout.addWidget(self.xdisplay, 3, 4, 1, 26) 92 | self.butn('pi', 0, 30, self.pi, colspan=6) 93 | self.butn('1/x', 1, 30, lambda state, op='1/x': self.func(op), colspan=6) 94 | self.butn('2x', 2, 30, lambda state, op='x*2': self.func(op), colspan=6) 95 | self.butn('x/2', 3, 30, lambda state, op='x/2': self.func(op), colspan=6) 96 | 97 | self.butn('mm -> in', 4, 0, self.mm2in, colspan=12) 98 | self.butn('in -> mm', 4, 12, self.in2mm, colspan=12) 99 | self.butn('STO', 4, 24, self.storex, clr=mygreen, colspan=6) 100 | self.butn('RCL', 4, 30, self.recallx, clr=mygreen, colspan=6) 101 | 102 | self.butn('7', 5, 0, lambda state, c='7': self.keyin(c), clr=myblue1) 103 | self.butn('8', 5, 6, lambda state, c='8': self.keyin(c), clr=myblue1) 104 | self.butn('9', 5, 12, lambda state, c='9': self.keyin(c), clr=myblue1) 105 | self.butn('+', 5, 18, lambda state, op='+': self.calculate(op), clr=myblue2) 106 | self.butn('R up', 5, 24, self.rotateup, clr=mygreen, colspan=6) 107 | self.butn('R dn', 5, 30, self.rotatedn, clr=mygreen, colspan=6) 108 | 109 | self.butn('4', 6, 0, lambda state, c='4': self.keyin(c), clr=myblue1) 110 | self.butn('5', 6, 6, lambda state, c='5': self.keyin(c), clr=myblue1) 111 | self.butn('6', 6, 12, lambda state, c='6': self.keyin(c), clr=myblue1) 112 | self.butn('-', 6, 18, lambda state, op='-': self.calculate(op), clr=myblue2) 113 | self.butn('<-', 6, 24, self.trimx, clr=myred, colspan=4) 114 | self.butn('X<>Y', 6, 28, self.swapxy, clr=mygreen, colspan=8) 115 | 116 | self.butn('1', 7, 0, lambda state, c='1': self.keyin(c), clr=myblue1) 117 | self.butn('2', 7, 6, lambda state, c='2': self.keyin(c), clr=myblue1) 118 | self.butn('3', 7, 12, lambda state, c='3': self.keyin(c), clr=myblue1) 119 | self.butn('*', 7, 18, lambda state, op='*': self.calculate(op), clr=myblue2) 120 | self.butn('CL X', 7, 24, self.clearx, clr=myred) 121 | self.butn('CLR', 7, 30, self.clearall, clr=myred) 122 | 123 | self.butn('0', 8, 0, lambda state, c='0': self.keyin(c), clr=myblue1) 124 | self.butn('.', 8, 6, lambda state, c='.': self.keyin(c), clr=myblue2) 125 | self.butn('+/-', 8, 12, lambda state, op='+/-': self.calculate(op), clr=myblue2) 126 | self.butn('/', 8, 18, lambda state, c='/': self.calculate(c), clr=myblue2) 127 | self.butn('ENTER', 8, 24, self.enter, clr=mygold, colspan=12) 128 | 129 | self.butn('Sin', 9, 0, 130 | lambda state, op='math.sin(x)': self.func(op, in_cnvrt=1), 131 | clr=mygold, colspan=8) 132 | self.butn('Cos', 9, 8, 133 | lambda state, op='math.cos(x)': self.func(op, in_cnvrt=1), 134 | clr=mygold, colspan=8) 135 | self.butn('Tan', 9, 16, 136 | lambda state, op='math.tan(x)': self.func(op, in_cnvrt=1), 137 | clr=mygold, colspan=8) 138 | self.butn('x^2', 9, 24, 139 | lambda state, op='x*x': self.func(op), clr=mygold) 140 | self.butn('10^x', 9, 30, lambda state, op='10**x': self.func(op), clr=mygold) 141 | self.butn('ASin', 10, 0, 142 | lambda state, op='math.asin(x)': self.func(op, out_cnvrt=1), 143 | clr=mygold, colspan=8) 144 | self.butn('ACos', 10, 8, 145 | lambda state, op='math.acos(x)': self.func(op, out_cnvrt=1), 146 | clr=mygold, colspan=8) 147 | self.butn('ATan', 10, 16, 148 | lambda state, op='math.atan(x)': self.func(op, out_cnvrt=1), 149 | clr=mygold, colspan=8) 150 | self.butn('Sqrt x', 10, 24, lambda state, op='math.sqrt(x)': self.func(op), clr=mygold) 151 | self.butn('y^x', 10, 30, lambda state, op='y**x': self.func(op), clr=mygold) 152 | 153 | self.butn('Dist', 11, 0, self.caller.distPtPt, clr=mygray, colspan=8) 154 | self.butn('Len', 11, 8, self.caller.edgeLen, clr=mygray, colspan=8) 155 | self.butn('Rad', 11, 16, self.noop, clr=mygray, colspan=8) 156 | self.butn('Ang', 11, 24, self.noop, clr=mygray) 157 | self.butn('', 11, 30, self.noop, clr=mygray) 158 | 159 | self.setLayout(self.mainLayout) 160 | 161 | def butn(self, text, row, col, com=None, clr='dimgray', rowspan=1, colspan=6): 162 | b = Button(text) 163 | b.clicked.connect(com) 164 | b.setStyleSheet('color: white; background-color: %s' % clr) 165 | self.mainLayout.addWidget(b, row, col, rowspan, colspan) 166 | 167 | def display(self): 168 | d = QLineEdit('0') 169 | d.setAlignment(QtCore.Qt.AlignRight) 170 | d.setMaxLength(18) 171 | font = d.font() 172 | font.setPointSize(font.pointSize() + 2) 173 | d.setFont(font) 174 | return d 175 | 176 | def closeEvent(self, event): 177 | print('calculator closing') 178 | try: 179 | self.caller.calculator = None 180 | except: 181 | pass 182 | event.accept() 183 | 184 | def pr(self, register): 185 | """Send value to caller.""" 186 | value = eval('self.'+register) 187 | if self.caller: 188 | self.caller.valueFromCalc(value) 189 | else: 190 | print(value) 191 | self.keip = False 192 | self.needrup = True 193 | 194 | def keyin(self, c): 195 | if self.keip: 196 | dispVal = self.xdisplay.text()+c 197 | self.xdisplay.setText(dispVal) 198 | self.x = float(dispVal) 199 | else: 200 | self.keip = True 201 | if self.needrup: 202 | self.rotateup(loop=0) 203 | self.xdisplay.setText('') 204 | if c == '.': 205 | c = '0.' 206 | self.keyin(c) 207 | 208 | def pi(self): 209 | self.rotateup() 210 | self.x = math.pi 211 | self.updateDisplays() 212 | self.needrup = True 213 | 214 | def updateDisplays(self): 215 | self.xdisplay.setText(str(self.x)) 216 | self.ydisplay.setText(str(self.y)) 217 | self.zdisplay.setText(str(self.z)) 218 | self.tdisplay.setText(str(self.t)) 219 | 220 | def enter(self): 221 | self.t = self.z 222 | self.z = self.y 223 | self.y = self.x 224 | self.x = self.x 225 | self.updateDisplays() 226 | self.keip = False 227 | self.needrup = False 228 | 229 | def calculate(self, op): 230 | """Arithmetic calculations between x and y registers, then rotate down.""" 231 | try: 232 | if op == '+/-': 233 | self.x = self.x * -1 234 | self.xdisplay.setText(str(self.x)) 235 | else: 236 | if op == '+': 237 | res = self.y + self.x 238 | elif op == '-': 239 | res = self.y - self.x 240 | elif op == '*': 241 | res = self.y * self.x 242 | elif op == '/': 243 | res = self.y / self.x 244 | self.x = res 245 | self.y = self.z 246 | self.z = self.t 247 | self.updateDisplays() 248 | self.keip = False 249 | self.needrup = True 250 | except: 251 | self.xdisplay.setText("ERROR") 252 | 253 | def func(self, op, in_cnvrt=0, out_cnvrt=0): 254 | """Evaluate function op then put result in x-register, don't rotate stack. 255 | if in_cnvrt: convert input value from degrees to radians. 256 | if out_cnvrt: convert output value from radians to degrees.""" 257 | x = self.x 258 | y = self.y 259 | if in_cnvrt: 260 | x = x * math.pi / 180 261 | result = eval(op) 262 | if out_cnvrt: 263 | result = result * 180 / math.pi 264 | self.x = result 265 | self.xdisplay.setText(str(self.x)) 266 | self.keip = False 267 | self.needrup = True 268 | 269 | def mm2in(self): 270 | if self.xdisplay.text(): 271 | self.x = self.x / 25.4 272 | self.xdisplay.setText(str(self.x)) 273 | self.keip = False 274 | self.needrup = True 275 | 276 | def in2mm(self): 277 | if self.xdisplay.text(): 278 | self.x = self.x * 25.4 279 | self.xdisplay.setText(str(self.x)) 280 | self.keip = False 281 | self.needrup = True 282 | 283 | def storex(self): 284 | self.mem = self.x 285 | self.keip = False 286 | self.needrup = True 287 | 288 | def recallx(self): 289 | self.rotateup() 290 | self.xdisplay.setText(str(self.mem)) 291 | self.keip = False 292 | self.needrup = True 293 | 294 | def rotateup(self, loop=1): 295 | x = self.t 296 | self.t = self.z 297 | self.z = self.y 298 | self.y = self.x 299 | if loop: 300 | self.x = x 301 | self.updateDisplays() 302 | 303 | def rotatedn(self): 304 | x = self.x 305 | self.x = self.y 306 | self.y = self.z 307 | self.z = self.t 308 | self.t = x 309 | self.updateDisplays() 310 | 311 | def trimx(self): 312 | trimmedStrVal = self.xdisplay.text()[:-1] 313 | try: 314 | self.xdisplay.setText(trimmedStrVal) 315 | self.x = float(trimmedStrVal) 316 | except ValueError: 317 | self.clearx() 318 | 319 | def swapxy(self): 320 | self.x, self.y = (self.y, self.x) 321 | self.updateDisplays() 322 | 323 | def clearx(self): 324 | self.x = 0 325 | self.xdisplay.setText('0') 326 | 327 | def clearall(self): 328 | self.x = self.y = self.z = self.t = 0 329 | self.updateDisplays() 330 | 331 | def putx(self, value): 332 | if self.needrup: 333 | self.rotateup(loop=0) 334 | self.x = value 335 | self.xdisplay.setText(str(value)) 336 | self.keip = False 337 | self.needrup = True 338 | 339 | def noop(self): 340 | pass 341 | 342 | 343 | if __name__ == '__main__': 344 | 345 | app = QApplication(sys.argv) 346 | calc = Calculator() 347 | sys.exit(calc.exec_()) 348 | -------------------------------------------------------------------------------- /sew.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2020 Doug Blanding (dblanding@gmail.com) 4 | # 5 | # This file is part of cadViewer. 6 | # The latest version of this file can be found at: 7 | # //https://github.com/dblanding/cadviewer 8 | # 9 | # cadViewer is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # cadViewer is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # if not, write to the Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | import sys 25 | from math import pi 26 | 27 | from OCC.BRepPrimAPI import BRepPrimAPI_MakeBox 28 | from OCC.AIS import * 29 | from OCC.Quantity import * 30 | from OCC.Display.SimpleGui import init_display 31 | from OCC.TopoDS import * 32 | from OCC.gp import * 33 | from OCC.TopLoc import * 34 | from OCC.Geom import * 35 | from OCC.BRep import BRep_Tool_Surface 36 | from OCC.GCE2d import * 37 | from OCC.BRepBuilderAPI import * 38 | from OCC.GC import * 39 | import aocutils.brep.solid_make 40 | 41 | display, start_display, add_menu, add_function_to_menu = init_display() 42 | 43 | 44 | def geom_plane_from_face(aFace): 45 | """ 46 | Returns the geometric plane entity from a planar surface 47 | """ 48 | return Handle_Geom_Plane.DownCast(OCC.BRep.BRep_Tool_Surface(aFace)).GetObject() 49 | 50 | def redraw(shape, event=None): 51 | # display with crisp edges and transpaarency 52 | context = display.Context 53 | context.RemoveAll() 54 | context.SetAutoActivateSelection(False) 55 | aisShape = AIS_Shape(shape) 56 | h_aisShape = aisShape.GetHandle() 57 | context.Display(h_aisShape) 58 | context.SetTransparency(h_aisShape, .1) 59 | context.HilightWithColor(h_aisShape, OCC.Quantity.Quantity_NOC_BLACK) 60 | display.FitAll() 61 | 62 | def makeBox(event=None): 63 | # Make a box 64 | Box = BRepPrimAPI_MakeBox(60, 60, 50).Shape() 65 | redraw() 66 | 67 | def rotateBox(): 68 | aisShape = AIS_Shape(Box) 69 | ax1 = gp_Ax1(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)) 70 | aRotTrsf = gp_Trsf() 71 | angle = pi/6 72 | aRotTrsf.SetRotation(ax1, angle) 73 | aTopLoc = TopLoc_Location(aRotTrsf) 74 | Box.Move(aTopLoc) 75 | redraw() 76 | 77 | def enableFaceSelect(event=None): 78 | display.selected_shape = None 79 | display.SetSelectionModeFace() 80 | 81 | def makeSqProfile(size, surface): 82 | # points and segments need to be in CW sequence to get W pointing along Z 83 | aPnt1 = gp_Pnt2d(-size, size) 84 | aPnt2 = gp_Pnt2d(size, size) 85 | aPnt3 = gp_Pnt2d(size, -size) 86 | aPnt4 = gp_Pnt2d(-size, -size) 87 | aSegment1 = GCE2d_MakeSegment(aPnt1, aPnt2) 88 | aSegment2 = GCE2d_MakeSegment(aPnt2, aPnt3) 89 | aSegment3 = GCE2d_MakeSegment(aPnt3, aPnt4) 90 | aSegment4 = GCE2d_MakeSegment(aPnt4, aPnt1) 91 | print 'Next is where something crashes' 92 | aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value(), 93 | Handle_Geom_Surface(surface)) 94 | aEdge2 = BRepBuilderAPI_MakeEdge(aSegment2.Value(), 95 | Handle_Geom_Surface(surface)) 96 | aEdge3 = BRepBuilderAPI_MakeEdge(aSegment3.Value(), 97 | Handle_Geom_Surface(surface)) 98 | aEdge4 = BRepBuilderAPI_MakeEdge(aSegment4.Value(), 99 | Handle_Geom_Surface(surface)) 100 | print "Doesn't get here (with rotated box)" 101 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), 102 | aEdge2.Edge(), 103 | aEdge3.Edge(), 104 | aEdge4.Edge()) 105 | 106 | myWireProfile = aWire.Wire() 107 | return myWireProfile # TopoDS_Wire 108 | 109 | def wireProfileOnFace(event=None): 110 | aShape = display.GetSelectedShape() 111 | shapes = display.GetSelectedShapes() 112 | face = None 113 | if aShape: 114 | face = topods_Face(aShape) 115 | print "A shape found:" 116 | elif shapes: 117 | aShape = shapes[0] 118 | face = topods_Face(aShape) 119 | print len(shapes), "Shapes found" 120 | if face: 121 | surface = geom_plane_from_face(face) 122 | wireProfile = makeSqProfile(50, surface) 123 | display.DisplayShape(wireProfile) 124 | else: 125 | print 'no face' 126 | 127 | def translatePnt(p1, vec): 128 | p2 = gp_Pnt() 129 | p2 = p1.Translated(vec) 130 | return p2 131 | 132 | def pointsToWire(p1, p2, p3, p4): 133 | seg1 = GC_MakeSegment(p1, p2) 134 | seg2 = GC_MakeSegment(p2, p3) 135 | seg3 = GC_MakeSegment(p3, p4) 136 | seg4 = GC_MakeSegment(p4, p1) 137 | edge1 = BRepBuilderAPI_MakeEdge(seg1.Value()) 138 | edge2 = BRepBuilderAPI_MakeEdge(seg2.Value()) 139 | edge3 = BRepBuilderAPI_MakeEdge(seg3.Value()) 140 | edge4 = BRepBuilderAPI_MakeEdge(seg4.Value()) 141 | wire = BRepBuilderAPI_MakeWire(edge1.Edge(), edge2.Edge(), 142 | edge3.Edge(), edge4.Edge()) 143 | return wire.Wire() 144 | 145 | def sewBox(): 146 | # Length of shape (spine) 147 | Vec = gp_Vec(0, 0, 10) 148 | # starting with bot vertices, make bot wire & face 149 | p1 = gp_Pnt(0, 0, 0) 150 | p2 = gp_Pnt(20, 0, 0) 151 | p3 = gp_Pnt(20, 20, 0) 152 | p4 = gp_Pnt(0, 20, 0) 153 | botWire = pointsToWire(p1, p2, p3, p4) 154 | botFace = BRepBuilderAPI_MakeFace(botWire).Face() 155 | # starting with topvertices, make top face 156 | p5 = translatePnt(p1, Vec) 157 | p6 = translatePnt(p2, Vec) 158 | p7 = translatePnt(p3, Vec) 159 | p8 = translatePnt(p4, Vec) 160 | topWire = pointsToWire(p5, p6, p7, p8) 161 | topFace = BRepBuilderAPI_MakeFace(topWire).Face() 162 | # Make spine (wire) to make 'pipe' 163 | spineSeg = GC_MakeSegment(p1, p5) 164 | spineEdge = BRepBuilderAPI_MakeEdge(spineSeg.Value()) 165 | spineWire = BRepBuilderAPI_MakeWire(spineEdge.Edge()).Wire() 166 | pipe = OCC.BRepOffsetAPI.BRepOffsetAPI_MakePipe(botWire, spineWire).Shape() 167 | # Sew together botFace, pipe, and topFace to get solid 168 | tolerance = 1e-6 169 | sew = OCC.BRepBuilderAPI.BRepBuilderAPI_Sewing(tolerance) 170 | sew.Add(botFace) 171 | sew.Add(pipe) 172 | sew.Add(topFace) 173 | sew.Perform() 174 | res = sew.SewedShape() 175 | print type(res) 176 | redraw(res) 177 | 178 | def exit(event=None): 179 | sys.exit() 180 | 181 | if __name__ == '__main__': 182 | add_menu('operations') 183 | add_function_to_menu('operations', makeBox) 184 | add_function_to_menu('operations', rotateBox) 185 | add_function_to_menu('operations', enableFaceSelect) 186 | add_function_to_menu('operations', wireProfileOnFace) 187 | add_function_to_menu('operations', exit) 188 | add_menu('Experimental') 189 | add_function_to_menu('Experimental', sewBox) 190 | 191 | start_display() 192 | -------------------------------------------------------------------------------- /stepXD.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2020 Doug Blanding (dblanding@gmail.com) 4 | # 5 | # This file is part of cadViewer. 6 | # The latest version of this file can be found at: 7 | # //https://github.com/dblanding/cadviewer 8 | # 9 | # cadViewer is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # cadViewer is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # if not, write to the Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | 25 | import logging 26 | import os.path 27 | import treelib 28 | from treemodel import TreeModel 29 | from OCC.Core.IFSelect import IFSelect_RetDone 30 | from OCC.Core.Quantity import Quantity_Color 31 | from OCC.Core.STEPCAFControl import STEPCAFControl_Reader 32 | from OCC.Core.TDF import TDF_Label, TDF_LabelSequence 33 | from OCC.Core.TopLoc import TopLoc_Location 34 | from OCC.Core.XCAFDoc import XCAFDoc_ColorSurf 35 | from OCC.Extend.TopologyUtils import TopologyExplorer 36 | 37 | logger = logging.getLogger(__name__) 38 | logger.setLevel(logging.DEBUG) # set to DEBUG | INFO | ERROR 39 | 40 | class StepImporter(): 41 | """Read .stp file, and create a TDocStd_Document OCAF document. 42 | 43 | Also, convert OCAF doc to a (disposable) treelib.Tree() structure. 44 | """ 45 | def __init__(self, filename, nextUID=0): 46 | 47 | self.filename = filename 48 | self.tree = treelib.tree.Tree() # 'disposable' ass'y structure 49 | self._currentUID = nextUID 50 | self.assyUidStack = [0] 51 | self.assyLocStack = [] 52 | self.doc = self.read_file() # TDocStd_Document 53 | 54 | def getNewUID(self): 55 | """Dispense a series of sequential integers as uids. 56 | 57 | Start with one more than the value held in win._currentUID. 58 | When finished, update the value held in win._currentUID.""" 59 | uid = self._currentUID + 1 60 | self._currentUID = uid 61 | return uid 62 | 63 | def getName(self, label): 64 | '''Get part name from label.''' 65 | return label.GetLabelName() 66 | 67 | def getColor(self, shape): 68 | # Get the part color 69 | #string_seq = self.layer_tool.GetObject().GetLayers(shape) 70 | color = Quantity_Color() 71 | self.color_tool.GetColor(shape, XCAFDoc_ColorSurf, color) 72 | logger.debug("color: %i, %i, %i", color.Red(), color.Green(), color.Blue()) 73 | return color 74 | 75 | def findComponents(self, label, comps): 76 | """Discover components from comps (LabelSequence) of an assembly (label). 77 | 78 | Components of an assembly are, by definition, references which refer to 79 | either a shape or another assembly. Components are essentially 'instances' 80 | of the referred shape or assembly, and carry a location vector specifing 81 | the location of the referred shape or assembly. 82 | """ 83 | logger.debug("") 84 | logger.debug("Finding components of label entry %s)", label.EntryDumpToString()) 85 | for j in range(comps.Length()): 86 | logger.debug("loop %i of %i", j+1, comps.Length()) 87 | cLabel = comps.Value(j+1) # component label 88 | cShape = self.shape_tool.GetShape(cLabel) 89 | logger.debug("Component number %i", j+1) 90 | logger.debug("Component entry: %s", cLabel.EntryDumpToString()) 91 | name = self.getName(cLabel) 92 | logger.debug("Component name: %s", name) 93 | refLabel = TDF_Label() # label of referred shape (or assembly) 94 | isRef = self.shape_tool.GetReferredShape(cLabel, refLabel) 95 | if isRef: # I think all components are references, but just in case... 96 | refShape = self.shape_tool.GetShape(refLabel) 97 | refLabelEntry = refLabel.EntryDumpToString() 98 | logger.debug("Entry referred to: %s", refLabelEntry) 99 | refName = self.getName(refLabel) 100 | logger.debug("Name of referred item: %s", refName) 101 | if self.shape_tool.IsSimpleShape(refLabel): 102 | logger.debug("Referred item is a Shape") 103 | logger.debug("Name of Shape: %s", refName) 104 | tempAssyLocStack = list(self.assyLocStack) 105 | tempAssyLocStack.reverse() 106 | 107 | for loc in tempAssyLocStack: 108 | cShape.Move(loc) 109 | 110 | color = self.getColor(refShape) 111 | self.tree.create_node(name, 112 | self.getNewUID(), 113 | self.assyUidStack[-1], 114 | {'a': False, 'l': None, 'c': color, 's': cShape}) 115 | elif self.shape_tool.IsAssembly(refLabel): 116 | logger.debug("Referred item is an Assembly") 117 | logger.debug("Name of Assembly: %s", refName) 118 | name = self.getName(cLabel) # Instance name 119 | aLoc = TopLoc_Location() 120 | # Location vector is carried by component 121 | aLoc = self.shape_tool.GetLocation(cLabel) 122 | self.assyLocStack.append(aLoc) 123 | newAssyUID = self.getNewUID() 124 | self.tree.create_node(name, 125 | newAssyUID, 126 | self.assyUidStack[-1], 127 | {'a': True, 'l': aLoc, 'c': None, 's': None}) 128 | self.assyUidStack.append(newAssyUID) 129 | rComps = TDF_LabelSequence() # Components of Assy 130 | subchilds = False 131 | isAssy = self.shape_tool.GetComponents(refLabel, rComps, subchilds) 132 | logger.debug("Assy name: %s", name) 133 | logger.debug("Is Assembly? %s", isAssy) 134 | logger.debug("Number of components: %s", rComps.Length()) 135 | if rComps.Length(): 136 | self.findComponents(refLabel, rComps) 137 | self.assyUidStack.pop() 138 | self.assyLocStack.pop() 139 | 140 | def read_file(self): 141 | """Build tree = treelib.Tree() to facilitate displaying the CAD model and 142 | 143 | constructing the tree view showing the assembly/component relationships. 144 | Each node of self.tree contains the following: 145 | (Name, UID, ParentUID, {Data}) where the Data keys are: 146 | 'a' (isAssy?), 'l' (TopLoc_Location), 'c' (Quantity_Color), 's' (TopoDS_Shape) 147 | """ 148 | logger.info("Reading STEP file") 149 | tmodel = TreeModel("STEP") 150 | self.shape_tool = tmodel.shape_tool 151 | self.color_tool = tmodel.color_tool 152 | 153 | step_reader = STEPCAFControl_Reader() 154 | step_reader.SetColorMode(True) 155 | step_reader.SetLayerMode(True) 156 | step_reader.SetNameMode(True) 157 | step_reader.SetMatMode(True) 158 | 159 | status = step_reader.ReadFile(self.filename) 160 | if status == IFSelect_RetDone: 161 | logger.info("Transfer doc to STEPCAFControl_Reader") 162 | step_reader.Transfer(tmodel.doc) 163 | 164 | labels = TDF_LabelSequence() 165 | self.shape_tool.GetShapes(labels) 166 | logger.info('Number of labels at root : %i', labels.Length()) 167 | try: 168 | rootlabel = labels.Value(1) # First label at root 169 | except RuntimeError: 170 | return 171 | name = self.getName(rootlabel) 172 | logger.info('Name of root label: %s', name) 173 | isAssy = self.shape_tool.IsAssembly(rootlabel) 174 | logger.info("First label at root holds an assembly? %s", isAssy) 175 | if isAssy: 176 | # If first label at root holds an assembly, it is the Top Assembly. 177 | # Through this label, the entire assembly is accessible. 178 | # there is no need to examine other labels at root explicitly. 179 | topLoc = TopLoc_Location() 180 | topLoc = self.shape_tool.GetLocation(rootlabel) 181 | self.assyLocStack.append(topLoc) 182 | entry = rootlabel.EntryDumpToString() 183 | logger.debug("Entry: %s", entry) 184 | logger.debug("Top assy name: %s", name) 185 | # Create root node for top assy 186 | newAssyUID = self.getNewUID() 187 | self.tree.create_node(name, newAssyUID, None, 188 | {'a': True, 'l': None, 'c': None, 's': None}) 189 | self.assyUidStack.append(newAssyUID) 190 | topComps = TDF_LabelSequence() # Components of Top Assy 191 | subchilds = False 192 | isAssy = self.shape_tool.GetComponents(rootlabel, topComps, subchilds) 193 | logger.debug("Is Assembly? %s", isAssy) 194 | logger.debug("Number of components: %s", topComps.Length()) 195 | logger.debug("Is Reference? %s", self.shape_tool.IsReference(rootlabel)) 196 | if topComps.Length(): 197 | self.findComponents(rootlabel, topComps) 198 | else: 199 | # Labels at root can hold solids or compounds (which are 'crude' assemblies) 200 | # Either way, we will need to create a root node in self.tree 201 | newAssyUID = self.getNewUID() 202 | self.tree.create_node(os.path.basename(self.filename), 203 | newAssyUID, None, 204 | {'a': True, 'l': None, 'c': None, 's': None}) 205 | self.assyUidStack = [newAssyUID] 206 | for j in range(labels.Length()): 207 | label = labels.Value(j+1) 208 | name = self.getName(label) 209 | isAssy = self.shape_tool.IsAssembly(label) 210 | logger.debug("Label %i is assembly?: %s", j+1, isAssy) 211 | shape = self.shape_tool.GetShape(label) 212 | color = self.getColor(shape) 213 | isSimpleShape = self.shape_tool.IsSimpleShape(label) 214 | logger.debug("Is Simple Shape? %s", isSimpleShape) 215 | shapeType = shape.ShapeType() 216 | logger.debug("The shape type is: %i", shapeType) 217 | if shapeType == 0: 218 | logger.debug("The shape type is OCC.Core.TopAbs.TopAbs_COMPOUND") 219 | topo = TopologyExplorer(shape) 220 | #topo = aocutils.topology.Topo(shape) 221 | logger.debug("Nb of compounds : %i", topo.number_of_compounds()) 222 | logger.debug("Nb of solids : %i", topo.number_of_solids()) 223 | logger.debug("Nb of shells : %i", topo.number_of_shells()) 224 | newAssyUID = self.getNewUID() 225 | for i, solid in enumerate(topo.solids()): 226 | name = "P%s" % str(i+1) 227 | self.tree.create_node(name, self.getNewUID(), 228 | self.assyUidStack[-1], 229 | {'a': False, 'l': None, 230 | 'c': color, 's': solid}) 231 | elif shapeType == 2: 232 | logger.debug("The shape type is OCC.Core.TopAbs.TopAbs_SOLID") 233 | self.tree.create_node(name, self.getNewUID(), 234 | self.assyUidStack[-1], 235 | {'a': False, 'l': None, 236 | 'c': color, 's': shape}) 237 | elif shapeType == 3: 238 | logger.debug("The shape type is OCC.Core.TopAbs.TopAbs_SHELL") 239 | self.tree.create_node(name, self.getNewUID(), 240 | self.assyUidStack[-1], 241 | {'a': False, 'l': None, 242 | 'c': color, 's': shape}) 243 | 244 | return tmodel.doc # 245 | -------------------------------------------------------------------------------- /treelib/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 2 | # Brett Alistair Kromkamp - brettkromkamp@gmail.com 3 | # Copyright (C) 2012-2017 4 | # Xiaming Chen - chenxm35@gmail.com 5 | # and other contributors. 6 | # All rights reserved. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | """ 20 | treelib - Python 2/3 Tree Implementation 21 | 22 | `treelib` is a Python module with two primary classes: Node and Tree. 23 | Tree is a self-contained structure with some nodes and connected by branches. 24 | A tree owns merely a root, while a node (except root) has some children and one parent. 25 | 26 | Note: To solve string compatibility between Python 2.x and 3.x, treelib follows 27 | the way of porting Python 3.x to 2/3. That means, all strings are manipulated as 28 | unicode and you do not need u'' prefix anymore. The impacted functions include `str()`, 29 | `show()` and `save2file()` routines. 30 | But if your data contains non-ascii characters and Python 2.x is used, 31 | you have to trigger the compatibility by declaring `unicode_literals` in the code: 32 | 33 | .. code-block:: python 34 | 35 | >>> from __future__ import unicode_literals 36 | """ 37 | __version__ = '1.5.5' 38 | 39 | from .tree import Tree 40 | from .node import Node 41 | -------------------------------------------------------------------------------- /treelib/exceptions.py: -------------------------------------------------------------------------------- 1 | class NodePropertyError(Exception): 2 | """Basic Node attribute error""" 3 | pass 4 | 5 | 6 | class NodeIDAbsentError(NodePropertyError): 7 | """Exception throwed if a node's identifier is unknown""" 8 | pass 9 | 10 | 11 | class NodePropertyAbsentError(NodePropertyError): 12 | """Exception throwed if a node's data property is not specified""" 13 | pass 14 | 15 | 16 | class MultipleRootError(Exception): 17 | """Exception throwed if more than one root exists in a tree.""" 18 | pass 19 | 20 | 21 | class DuplicatedNodeIdError(Exception): 22 | """Exception throwed if an identifier already exists in a tree.""" 23 | pass 24 | 25 | 26 | class LinkPastRootNodeError(Exception): 27 | """ 28 | Exception throwed in Tree.link_past_node() if one attempts 29 | to "link past" the root node of a tree. 30 | """ 31 | pass 32 | 33 | 34 | class InvalidLevelNumber(Exception): 35 | pass 36 | 37 | 38 | class LoopError(Exception): 39 | """ 40 | Exception thrown if trying to move node B to node A's position 41 | while A is B's ancestor. 42 | """ 43 | pass 44 | 45 | 46 | -------------------------------------------------------------------------------- /treelib/node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2011 3 | # Brett Alistair Kromkamp - brettkromkamp@gmail.com 4 | # Copyright (C) 2012-2017 5 | # Xiaming Chen - chenxm35@gmail.com 6 | # and other contributors. 7 | # All rights reserved. 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | """ 21 | Node structure in treelib. 22 | 23 | A :class:`Node` object contains basic properties such as node identifier, 24 | node tag, parent node, children nodes etc., and some operations for a node. 25 | """ 26 | import uuid 27 | 28 | from .exceptions import NodePropertyError 29 | 30 | 31 | class Node(object): 32 | """ 33 | Nodes are elementary objects that are stored in the `_nodes` dictionary of a Tree. 34 | Use `data` attribute to store node-specific data. 35 | """ 36 | 37 | #: Mode constants for routine `update_fpointer()`. 38 | (ADD, DELETE, INSERT, REPLACE) = list(range(4)) 39 | 40 | def __init__(self, tag=None, identifier=None, expanded=True, data=None): 41 | """Create a new Node object to be placed inside a Tree object""" 42 | 43 | #: if given as a parameter, must be unique 44 | self._identifier = None 45 | self._set_identifier(identifier) 46 | 47 | #: None or something else 48 | #: if None, self._identifier will be set to the identifier's value. 49 | if tag is None: 50 | self._tag = self._identifier 51 | else: 52 | self._tag = tag 53 | 54 | #: boolean 55 | self.expanded = expanded 56 | 57 | #: identifier of the parent's node : 58 | self._bpointer = None 59 | #: identifier(s) of the soons' node(s) : 60 | self._fpointer = list() 61 | 62 | #: User payload associated with this node. 63 | self.data = data 64 | 65 | def __lt__(self, other): 66 | return self.tag < other.tag 67 | 68 | def _set_identifier(self, nid): 69 | """Initialize self._set_identifier""" 70 | if nid is None: 71 | self._identifier = str(uuid.uuid1()) 72 | else: 73 | self._identifier = nid 74 | 75 | @property 76 | def bpointer(self): 77 | """ 78 | The parent ID of a node. This attribute can be 79 | accessed and modified with ``.`` and ``=`` operator respectively. 80 | """ 81 | return self._bpointer 82 | 83 | @bpointer.setter 84 | def bpointer(self, nid): 85 | """Set the value of `_bpointer`.""" 86 | if nid is not None: 87 | self._bpointer = nid 88 | else: 89 | # print("WARNING: the bpointer of node %s " \ 90 | # "is set to None" % self._identifier) 91 | self._bpointer = None 92 | 93 | @property 94 | def fpointer(self): 95 | """ 96 | With a getting operator, a list of IDs of node's children is obtained. With 97 | a setting operator, the value can be list, set, or dict. For list or set, 98 | it is converted to a list type by the package; for dict, the keys are 99 | treated as the node IDs. 100 | """ 101 | return self._fpointer 102 | 103 | @fpointer.setter 104 | def fpointer(self, value): 105 | """Set the value of `_fpointer`.""" 106 | if value is None: 107 | self._fpointer = list() 108 | elif isinstance(value, list): 109 | self._fpointer = value 110 | elif isinstance(value, dict): 111 | self._fpointer = list(value.keys()) 112 | elif isinstance(value, set): 113 | self._fpointer = list(value) 114 | else: # TODO: add deprecated routine 115 | pass 116 | 117 | @property 118 | def identifier(self): 119 | """ 120 | The unique ID of a node within the scope of a tree. This attribute can be 121 | accessed and modified with ``.`` and ``=`` operator respectively. 122 | """ 123 | return self._identifier 124 | 125 | @identifier.setter 126 | def identifier(self, value): 127 | """Set the value of `_identifier`.""" 128 | if value is None: 129 | print("WARNING: node ID can not be None") 130 | else: 131 | self._set_identifier(value) 132 | 133 | def is_leaf(self): 134 | """Return true if current node has no children.""" 135 | if len(self.fpointer) == 0: 136 | return True 137 | else: 138 | return False 139 | 140 | def is_root(self): 141 | """Return true if self has no parent, i.e. as root.""" 142 | return self._bpointer is None 143 | 144 | @property 145 | def tag(self): 146 | """ 147 | The readable node name for human. This attribute can be accessed and 148 | modified with ``.`` and ``=`` operator respectively. 149 | """ 150 | return self._tag 151 | 152 | @tag.setter 153 | def tag(self, value): 154 | """Set the value of `_tag`.""" 155 | self._tag = value if value is not None else None 156 | 157 | def update_bpointer(self, nid): 158 | """Set the parent (indicated by the ``nid`` parameter) of a node.""" 159 | self.bpointer = nid 160 | 161 | def update_fpointer(self, nid, mode=ADD, replace=None): 162 | """ 163 | Update the children list with different modes: addition (Node.ADD or 164 | Node.INSERT) and deletion (Node.DELETE). 165 | """ 166 | if nid is None: 167 | return 168 | 169 | if mode is self.ADD: 170 | self._fpointer.append(nid) 171 | 172 | elif mode is self.DELETE: 173 | if nid in self._fpointer: 174 | self._fpointer.remove(nid) 175 | 176 | elif mode is self.INSERT: # deprecate to ADD mode 177 | print("WARNING: INSERT is deprecated to ADD mode") 178 | self.update_fpointer(nid) 179 | 180 | elif mode is self.REPLACE: 181 | if replace is None: 182 | raise NodePropertyError( 183 | 'Argument "repalce" should be provided when mode is {}'.format(mode) 184 | ) 185 | 186 | ind = self._fpointer.index(nid) 187 | self._fpointer[ind] = replace 188 | 189 | def __repr__(self): 190 | name = self.__class__.__name__ 191 | kwargs = [ 192 | "tag={0}".format(self.tag), 193 | "identifier={0}".format(self.identifier), 194 | "data={0}".format(self.data), 195 | ] 196 | return "%s(%s)" % (name, ", ".join(kwargs)) 197 | -------------------------------------------------------------------------------- /treelib/plugins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright (C) 2011 4 | # Brett Alistair Kromkamp - brettkromkamp@gmail.com 5 | # Copyright (C) 2012-2017 6 | # Xiaming Chen - chenxm35@gmail.com 7 | # and other contributors. 8 | # All rights reserved. 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | """ 22 | This is a public location to maintain contributed 23 | utilities to extend the basic Tree class. 24 | 25 | Deprecated! We prefer a unified processing of Tree object. 26 | """ 27 | from __future__ import unicode_literals 28 | 29 | import codecs 30 | 31 | 32 | def export_to_dot(tree, filename=None, shape='circle', graph='digraph'): 33 | """Exports the tree in the dot format of the graphviz software""" 34 | print('Deprecated module. Use `tree.to_graphviz()` instead.') 35 | tree.to_graphviz(filename=filename, shape=shape, graph=graph) 36 | -------------------------------------------------------------------------------- /treemodel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2020 Doug Blanding (dblanding@gmail.com) 4 | # 5 | # This file is part of cadViewer. 6 | # The latest version of this file can be found at: 7 | # //https://github.com/dblanding/cadviewer 8 | # 9 | # cadViewer is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # cadViewer is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # if not, write to the Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | import logging 25 | from OCC.Core.TCollection import TCollection_ExtendedString 26 | from OCC.Core.TDF import TDF_ChildIterator 27 | from OCC.Core.TDocStd import TDocStd_Document 28 | from OCC.Core.XCAFApp import XCAFApp_Application_GetApplication 29 | from OCC.Core.XCAFDoc import (XCAFDoc_DocumentTool_ShapeTool, 30 | XCAFDoc_DocumentTool_ColorTool, 31 | XCAFDoc_DocumentTool_LayerTool, 32 | XCAFDoc_DocumentTool_MaterialTool) 33 | 34 | logger = logging.getLogger(__name__) 35 | logger.setLevel(logging.DEBUG) # set to DEBUG | INFO | ERROR 36 | 37 | 38 | class TreeModel(): 39 | """XCAF Tree Model of heirarchical CAD assembly data.""" 40 | 41 | def __init__(self, title): 42 | # Create the application and document 43 | doc = TDocStd_Document(TCollection_ExtendedString(title)) 44 | app = XCAFApp_Application_GetApplication() 45 | app.NewDocument(TCollection_ExtendedString("MDTV-CAF"), doc) 46 | self.app = app 47 | self.doc = doc 48 | # Initialize tools 49 | self.shape_tool = XCAFDoc_DocumentTool_ShapeTool(doc.Main()) 50 | self.shape_tool.SetAutoNaming(True) 51 | self.color_tool = XCAFDoc_DocumentTool_ColorTool(doc.Main()) 52 | self.layer_tool = XCAFDoc_DocumentTool_LayerTool(doc.Main()) 53 | self.l_materials = XCAFDoc_DocumentTool_MaterialTool(doc.Main()) 54 | self.allChildLabels = [] 55 | 56 | def getChildLabels(self, label): 57 | """Return list of child labels directly below label.""" 58 | itlbl = TDF_ChildIterator(label, True) 59 | childlabels = [] 60 | while itlbl.More(): 61 | childlabels.append(itlbl.Value()) 62 | itlbl.Next() 63 | return childlabels 64 | 65 | def getAllChildLabels(self, label, first=True): 66 | """Return list of all child labels (recursively) below label. 67 | 68 | This doesn't find anything at the second level down because 69 | the component labels of root do not have children, but rather 70 | they have references.""" 71 | print("Entering 'getAllChildLabels'") 72 | if first: 73 | self.allChildLabels = [] 74 | childLabels = self.getChildLabels(label) 75 | print(f"len(childLabels) = {len(childLabels)}") 76 | self.allChildLabels += childLabels 77 | print(f"len(allChildLabels) = {len(self.allChildLabels)}") 78 | for lbl in childLabels: 79 | self.getAllChildLabels(lbl, first=False) 80 | return self.allChildLabels 81 | 82 | def saveDoc(self, filename): 83 | """Save doc to file (for educational purposes) (not working yet) 84 | """ 85 | logger.debug("Saving doc to file") 86 | savefilename = TCollection_ExtendedString(filename) 87 | self.app.SaveAs(self.doc, savefilename) 88 | -------------------------------------------------------------------------------- /versioning.txt: -------------------------------------------------------------------------------- 1 | 2 | Thoughts on versioning: 3 | Assign a version number once the old code is working with the current 4 | version of PythonOCC. Use a three field version number like so: 5 | 6 | '0.74.1' 7 | 8 | First field is '0' 9 | 10 | When the app has enough functionality to be 'USEFUL' to somebody, then 11 | a case could be made for changing this to '1'. For example, if STEP read 12 | and write could support a Round Trip (loading a STEP file, modifying it, 13 | then writing it back out), that would be 'useful'. 14 | 15 | Second field is '74' 16 | 17 | This corresponds to the pyocc version with which it works. As witnessed 18 | by the recent problem caused by neglect of the code from 2016 until 2019, 19 | if it doesn't stay in sync with PythonOCC, it has little value. 20 | 21 | Third field is '1' 22 | 23 | Increment this number with significant imporvements to functionality. 24 | Reset to '1' if either of the first two fields are incremented. 25 | -------------------------------------------------------------------------------- /waysToMoveShape.txt: -------------------------------------------------------------------------------- 1 | Ways to move a TopoDS_shape: 2 | 3 | # TopLoc_Location 4 | # as shown in 5 | tFace = BRepBuilderAPI_MakeFace(mFace).Face() 6 | faceNormal = Construct.face_normal(mFace) 7 | vctr = gp_Vec(faceNormal).Multiplied(value) 8 | trsf = gp_Trsf() 9 | trsf.SetTranslation(vctr) 10 | tFace.Move(TopLoc_Location(trsf)) 11 | 12 | # BRepBuilderAPI_Transform 13 | # as shown in core_topology_boolean.py 14 | 15 | def translate_topods_from_vector(brep_or_iterable, vec, copy=False): 16 | ''' 17 | translate a brep over a vector 18 | @param brep: the Topo_DS to translate 19 | @param vec: the vector defining the translation 20 | @param copy: copies to brep if True 21 | ''' 22 | trns = gp_Trsf() 23 | trns.SetTranslation(vec) 24 | brep_trns = BRepBuilderAPI_Transform(brep_or_iterable, trns, copy) 25 | brep_trns.Build() 26 | return brep_trns.Shape() 27 | 28 | 29 | # as shown in core_classic_occ_bottle.py 30 | # Create a wire out of the edges 31 | aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), aEdge2.Edge(), aEdge3.Edge()) 32 | 33 | # Quick way to specify the X axis 34 | xAxis = gp_OX() 35 | 36 | # Set up the mirror 37 | aTrsf = gp_Trsf() 38 | aTrsf.SetMirror(xAxis) 39 | 40 | # Apply the mirror transformation 41 | aBRespTrsf = BRepBuilderAPI_Transform(aWire.Wire(), aTrsf) 42 | 43 | # Get the mirrored shape back out of the transformation and convert back to a wire 44 | aMirroredShape = aBRespTrsf.Shape() 45 | 46 | # A wire instead of a generic shape now 47 | aMirroredWire = topods.Wire(aMirroredShape) 48 | 49 | --------------------------------------------------------------------------------