• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eX-Mech / pymech / 3790187723

pending completion
3790187723

Pull #84

github

GitHub
Merge a302eba36 into ccf4f61e9
Pull Request #84: Enh/meshplot

156 of 156 new or added lines in 1 file covered. (100.0%)

2376 of 3031 relevant lines covered (78.39%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pymech/meshplot.py
1
import wx
×
2
from wx import glcanvas
×
3
from OpenGL.GL import glViewport, glMatrixMode, glClearColor, glClear, glLoadIdentity, glOrtho, glEnable, glLineWidth, glBegin, glEnd, glColor, glVertex3fv, GL_PROJECTION, GL_COLOR_BUFFER_BIT, GL_LINE_SMOOTH, GL_MODELVIEW, GL_LINES
×
4
from math import sqrt, atan2, asin, cos, sin
×
5

6

7
class MeshFrame(wx.Frame):
×
8
    """
9
    A frame to display meshes
10
    """
11

12
    def __init__(self,
×
13
                 mesh,
14
                 parent,
15
                 id,
16
                 title,
17
                 pos=wx.DefaultPosition,
18
                 size=wx.DefaultSize,
19
                 style=wx.DEFAULT_FRAME_STYLE,
20
                 name='frame'
21
                 ):
22
        super(MeshFrame, self).__init__(parent, id, title, pos, size, style, name)
×
23
        self.GLinitialized = False
×
24
        attribList = (glcanvas.WX_GL_RGBA,  # RGBA
×
25
                      glcanvas.WX_GL_DOUBLEBUFFER,  # Double Buffered
26
                      glcanvas.WX_GL_DEPTH_SIZE, 24)  # 24 bit
27

28
        # Create the canvas
29
        self.canvas = glcanvas.GLCanvas(self, attribList=attribList)
×
30
        self.context = glcanvas.GLContext(self.canvas)
×
31

32
        # Set the event handlers.
33
        self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent)
×
34
        self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent)
×
35
        self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent)
×
36

37
        # create a menu bar
38
        # self.makeMenuBar()
39

40
        # mesh display properties
41
        self.curve_points = 12
×
42

43
        # data to be drawn
44
        self.vertices = []
×
45
        self.edges = []
×
46
        self.buildMesh(mesh)
×
47

48
        # view parameters
49
        self.margins = 0.05
×
50
        # sets self.mesh_limits
51
        self.setLimits(mesh)
×
52
        # current limits
53
        self.limits = self.mesh_limits
×
54

55
        # and a status bar
56
        # self.CreateStatusBar()
57
        # self.SetStatusText("initialised")
58

59
    # Canvas Proxy Methods
60

61
    def GetGLExtents(self):
×
62
        """Get the extents of the OpenGL canvas."""
63
        return self.canvas.GetClientSize()
×
64

65
    def SwapBuffers(self):
×
66
        """Swap the OpenGL buffers."""
67
        self.canvas.SwapBuffers()
×
68

69
    def OnExit(self, event):
×
70
        """Close the frame, terminating the application."""
71
        self.Close(True)
×
72

73
    # wxPython Window Handlers
74

75
    def processEraseBackgroundEvent(self, event):
×
76
        """Process the erase background event."""
77
        pass  # Do nothing, to avoid flashing on MSWin
×
78

79
    def processSizeEvent(self, event):
×
80
        """Process the resize event."""
81
        if self.context:
×
82
            # Make sure the frame is shown before calling SetCurrent.
83
            self.Show()
×
84
            self.canvas.SetCurrent(self.context)
×
85

86
            size = self.GetGLExtents()
×
87
            self.OnReshape(size.width, size.height)
×
88
            self.canvas.Refresh(False)
×
89
        event.Skip()
×
90

91
    def processPaintEvent(self, event):
×
92
        """Process the drawing event."""
93
        self.canvas.SetCurrent(self.context)
×
94

95
        # This is a 'perfect' time to initialize OpenGL ... only if we need to
96
        if not self.GLinitialized:
×
97
            self.OnInitGL()
×
98
            self.GLinitialized = True
×
99

100
        self.OnDraw()
×
101
        event.Skip()
×
102

103
    # GLFrame OpenGL Event Handlers
104

105
    def OnInitGL(self):
×
106
        """Initialize OpenGL for use in the window."""
107
        glClearColor(1, 1, 1, 1)
×
108

109
    def OnReshape(self, width, height):
×
110
        """Reshape the OpenGL viewport based on the dimensions of the window."""
111

112
        xmin = self.limits[0]
×
113
        xmax = self.limits[1]
×
114
        ymin = self.limits[2]
×
115
        ymax = self.limits[3]
×
116
        # check whether the view is limited by width or height, and scale accordingly
117
        lx = xmax - xmin
×
118
        ly = ymax - ymin
×
119
        if lx / width > ly / height:
×
120
            y0 = 0.5 * (ymin + ymax)
×
121
            dy = height / width * lx / 2
×
122
            ymin = y0 - dy
×
123
            ymax = y0 + dy
×
124
        else:
125
            x0 = 0.5 * (xmin + xmax)
×
126
            dx = width / height * ly / 2
×
127
            xmin = x0 - dx
×
128
            xmax = x0 + dx
×
129
        glViewport(0, 0, width, height)
×
130
        glMatrixMode(GL_PROJECTION)
×
131
        glLoadIdentity()
×
132
        glOrtho(
×
133
            xmin,
134
            xmax,
135
            ymin,
136
            ymax,
137
            -1,
138
            1
139
        )
140

141
        glMatrixMode(GL_MODELVIEW)
×
142
        glLoadIdentity()
×
143

144
    def OnDraw(self, *args, **kwargs):
×
145
        "Draw the window."
146
        glClear(GL_COLOR_BUFFER_BIT)
×
147
        glEnable(GL_LINE_SMOOTH)  # this doesn't seem to be doing anything? It would be nice to have antialiasing
×
148
        glLineWidth(1.0)
×
149
        glBegin(GL_LINES)
×
150
        glColor(0, 0, 0)
×
151
        for edge in self.edges:
×
152
            for vertex in edge:
×
153
                glVertex3fv(self.vertices[vertex])
×
154
        glEnd()
×
155

156
        self.SwapBuffers()
×
157

158
    def buildMesh(self, mesh):
×
159
        """
160
        Builds the edges to be drawn based on the mesh representation.
161
        """
162

163
        # gives the indices of the vertices of an element in a position array
164
        def vertex_indices(iface):
×
165
            if iface == 0:
×
166
                return (0, 0)
×
167
            elif iface == 1:
×
168
                return (0, -1)
×
169
            elif iface == 2:
×
170
                return (-1, -1)
×
171
            else:
172
                return (-1, 0)
×
173

174
        current_point = 0
×
175
        first_point = 0
×
176
        for el in mesh.elem:
×
177
            first_point = current_point
×
178
            for iface in range(4):
×
179
                j0, i0 = vertex_indices(iface)
×
180
                if el.ccurv[iface] == '':
×
181
                    self.vertices.append((
×
182
                        el.pos[0, 0, j0, i0],
183
                        el.pos[1, 0, j0, i0],
184
                        0.,
185
                    ))
186
                    if iface < 3:
×
187
                        next_point = current_point + 1
×
188
                    else:
189
                        next_point = first_point
×
190
                    self.edges.append((current_point, next_point))
×
191
                    current_point += 1
×
192
                elif el.ccurv[iface] == 'm':
×
193
                    # we should draw a parabola passing through the current vertex, the midpoint, and the next vertex.
194
                    x0, y0 = el.pos[0:2, 0, j0, i0]
×
195
                    xm, ym = el.curv[iface][0:2]
×
196
                    j1, i1 = vertex_indices((iface + 1) % 4)
×
197
                    x1, y1 = el.pos[0:2, 0, j1, i1]
×
198
                    # quadratic Lagrange interpolation between points
199
                    for ipt in range(self.curve_points):
×
200
                        # tp varies between 0 and 1
201
                        tp = ipt / self.curve_points
×
202
                        xp = (
×
203
                            x0 * 2 * (tp - 0.5) * (tp - 1)
204
                            - xm * 4 * tp * (tp - 1)
205
                            + x1 * 2 * tp * (tp - 0.5)
206
                        )
207
                        yp = (
×
208
                            y0 * 2 * (tp - 0.5) * (tp - 1)
209
                            - ym * 4 * tp * (tp - 1)
210
                            + y1 * 2 * tp * (tp - 0.5)
211
                        )
212
                        self.vertices.append((xp, yp, 0))
×
213
                        if iface == 3 and ipt == self.curve_points - 1:
×
214
                            next_point = first_point
×
215
                        else:
216
                            next_point = current_point + 1
×
217
                        self.edges.append((current_point, next_point))
×
218
                        current_point += 1
×
219
                elif el.ccurv[iface] == 'C':
×
220
                    # draw a circle of given radius passing through the next vertex and the current one
221
                    # first, find the distance between the midpoint of the segment ((x0, y0), (x1, y1)) and the center (xc, yc) of the circle
222
                    radius = el.curv[iface][0]  # this can be positive or negative depending on direction
×
223
                    x0, y0 = el.pos[0:2, 0, j0, i0]
×
224
                    j1, i1 = vertex_indices((iface + 1) % 4)
×
225
                    x1, y1 = el.pos[0:2, 0, j1, i1]
×
226
                    # length of the segment
227
                    ls2 = (x1 - x0) ** 2 + (y1 - y0) ** 2
×
228
                    try:
×
229
                        dist = radius * sqrt(1 - ls2 / (4 * radius ** 2))
×
230
                    except ValueError:
×
231
                        raise ValueError("the radius of the curved edge is too small")
232
                    # midpoint of the edge
233
                    xm = 0.5 * (x0 + x1)
×
234
                    ym = 0.5 * (y0 + y1)
×
235
                    # outward normal direction
236
                    ls = sqrt(ls2)
×
237
                    nx = (y1 - y0) / ls
×
238
                    ny = -(x1 - x0) / ls
×
239
                    # position of the centre
240
                    xc = xm - nx * dist
×
241
                    yc = ym - ny * dist
×
242
                    # now find the range of arguments spanned by the circle arc
243
                    # argument to the centre of the edge
244
                    theta0 = atan2(ym - yc, xm - xc)
×
245
                    dtheta = asin(ls / (2 * radius))
×
246
                    theta_min = theta0 - dtheta
×
247
                    # Now, add the points
248
                    for itheta in range(self.curve_points):
×
249
                        theta = theta_min + 2 * dtheta * itheta / self.curve_points
×
250
                        xp = xc + abs(radius) * cos(theta)
×
251
                        yp = yc + abs(radius) * sin(theta)
×
252
                        self.vertices.append((xp, yp, 0))
×
253
                        if iface == 3 and itheta == self.curve_points - 1:
×
254
                            next_point = first_point
×
255
                        else:
256
                            next_point = current_point + 1
×
257
                        self.edges.append((current_point, next_point))
×
258
                        current_point += 1
×
259

260
    def setLimits(self, mesh):
×
261
        """
262
        set view limits to the size of the mesh with some margin
263
        """
264
        xmin, xmax = mesh.lims.pos[0]
×
265
        ymin, ymax = mesh.lims.pos[1]
×
266
        lx = xmax - xmin
×
267
        ly = ymax - ymin
×
268
        self.mesh_limits = [
×
269
            xmin - self.margins * lx,
270
            xmax + self.margins * lx,
271
            ymin - self.margins * ly,
272
            ymax + self.margins * ly,
273
        ]
274

275

276
def plot2D(mesh):
×
277
    # make a new app & frame
278
    app = wx.App()
×
279
    frame = MeshFrame(mesh, None, -1, title="pymech")
×
280

281
    frame.Show()
×
282

283
    # Start the event loop.
284
    app.MainLoop()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc