>>')
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 | 
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 |
--------------------------------------------------------------------------------