Пример #1
0
class GcodeViewerScreen(Screen):
    current_z = NumericProperty(0)
    select_mode = BooleanProperty(False)
    twod_mode = BooleanProperty(False)
    laser_mode = BooleanProperty(False)
    valid = BooleanProperty(False)

    def __init__(self, comms=None, **kwargs):
        super(GcodeViewerScreen, self).__init__(**kwargs)
        self.app = App.get_running_app()
        self.last_file_pos = None
        self.canv = InstructionGroup()
        self.bind(pos=self._redraw, size=self._redraw)
        self.last_target_layer = 0
        self.tx = 0
        self.ty = 0
        self.scale = 1.0
        self.comms = comms
        self.twod_mode = self.app.is_cnc
        self.rval = 0.0

    def loading(self, ll=1):
        self.valid = False
        self.li = Image(source='img/image-loading.gif')
        self.add_widget(self.li)
        self.ids.surface.canvas.remove(self.canv)
        threading.Thread(target=self._load_file, args=(ll, )).start()

    @mainthread
    def _loaded(self):
        Logger.debug("GcodeViewerScreen: in _loaded. ok: {}".format(
            self._loaded_ok))
        self.remove_widget(self.li)
        self.li = None
        self.ids.surface.canvas.add(self.canv)
        self.valid = self._loaded_ok
        if self._loaded_ok:
            # not sure why we need to do this
            self.ids.surface.top = Window.height
            if self.app.is_connected:
                self.app.bind(wpos=self.update_tool)

    def _load_file(self, ll):
        self._loaded_ok = False
        try:
            self.parse_gcode_file(self.app.gcode_file, ll, True)
        except Exception:
            print(traceback.format_exc())
            mb = MessageBox(
                text='File not found: {}'.format(self.app.gcode_file))
            mb.open()

        self._loaded()

    def _redraw(self, instance, value):
        self.ids.surface.canvas.remove(self.canv)
        self.ids.surface.canvas.add(self.canv)

    def clear(self):
        self.app.unbind(wpos=self.update_tool)

        if self.li:
            self.remove_widget(self.li)
            self.li = None

        if self.select_mode:
            self.stop_cursor(0, 0)
            self.select_mode = False
            self.ids.select_mode_but.state = 'normal'

        self.valid = False
        self.is_visible = False
        self.canv.clear()
        self.ids.surface.canvas.remove(self.canv)

        self.last_target_layer = 0
        # reset scale and translation
        m = Matrix()
        m.identity()
        self.ids.surface.transform = m
        # not sure why we need to do this
        self.ids.surface.top = Window.height

    def next_layer(self):
        self.loading(self.last_target_layer + 1)

    def prev_layer(self):
        n = 1 if self.last_target_layer <= 1 else self.last_target_layer - 1
        self.loading(n)

    def print(self):
        self.app.main_window._start_print()

    # ----------------------------------------------------------------------
    # Return center x,y,z,r for arc motions 2,3 and set self.rval
    # Cribbed from bCNC
    # ----------------------------------------------------------------------

    def motionCenter(self,
                     gcode,
                     plane,
                     xyz_cur,
                     xyz_val,
                     ival,
                     jval,
                     kval=0.0):
        if self.rval > 0.0:
            if plane == XY:
                x = xyz_cur[0]
                y = xyz_cur[1]
                xv = xyz_val[0]
                yv = xyz_val[1]
            elif plane == XZ:
                x = xyz_cur[0]
                y = xyz_cur[2]
                xv = xyz_val[0]
                yv = xyz_val[2]
            else:
                x = xyz_cur[1]
                y = xyz_cur[2]
                xv = xyz_val[1]
                yv = xyz_val[2]

            ABx = xv - x
            ABy = yv - y
            Cx = 0.5 * (x + xv)
            Cy = 0.5 * (y + yv)
            AB = math.sqrt(ABx**2 + ABy**2)
            try:
                OC = math.sqrt(self.rval**2 - AB**2 / 4.0)
            except Exception:
                OC = 0.0

            if gcode == 2:
                OC = -OC  # CW
            if AB != 0.0:
                return Cx - OC * ABy / AB, Cy + OC * ABx / AB
            else:
                # Error!!!
                return x, y
        else:
            # Center
            xc = xyz_cur[0] + ival
            yc = xyz_cur[1] + jval
            zc = xyz_cur[2] + kval
            self.rval = math.sqrt(ival**2 + jval**2 + kval**2)

            if plane == XY:
                return xc, yc
            elif plane == XZ:
                return xc, zc
            else:
                return yc, zc

    extract_gcode = re.compile(r"(G|X|Y|Z|I|J|K|E|S)(-?\d*\.?\d*\.?)")

    def parse_gcode_file(self, fn, target_layer=0, one_layer=False):
        # open file parse gcode and draw
        Logger.debug("GcodeViewerScreen: parsing file {}".format(fn))
        lastpos = [self.app.wpos[0], self.app.wpos[1],
                   -1]  # XYZ, set to initial tool position
        lastz = None
        lastdeltaz = None
        laste = 0
        lasts = 1
        layer = -1
        last_gcode = -1
        points = []
        max_x = float('nan')
        max_y = float('nan')
        min_x = float('nan')
        min_y = float('nan')
        has_e = False
        plane = XY
        rel_move = False
        self.is_visible = True
        if self.laser_mode:
            self.twod_mode = True  # laser mode implies 2D mode

        self.last_target_layer = target_layer

        # reset scale and translation
        m = Matrix()
        m.identity()
        self.ids.surface.transform = m

        # remove all instructions from canvas
        self.canv.clear()

        self.canv.add(PushMatrix())
        modal_g = 0
        cnt = 0
        found_layer = False
        x = lastpos[0]
        y = lastpos[1]
        z = lastpos[2]

        with open(fn) as f:
            # if self.last_file_pos:
            #     # jump to last read position
            #     f.seek(self.last_file_pos)
            #     self.last_file_pos= None
            #     print('Jumped to Saved position: {}'.format(self.last_file_pos))
            for ln in f:
                cnt += 1
                ln = ln.strip()
                if not ln: continue
                if ln.startswith(';'): continue
                if ln.startswith('('): continue
                p = ln.find(';')
                if p >= 0: ln = ln[:p]
                matches = self.extract_gcode.findall(ln)

                # this handles multiple G codes on one line
                gcodes = []
                d = {}
                for m in matches:
                    #print(m)
                    if m[0] == 'G' and 'G' in d:
                        # we have another G code on the same line
                        gcodes.append(d)
                        d = {}
                    d[m[0]] = float(m[1])

                gcodes.append(d)

                for d in gcodes:
                    if not d: continue

                    Logger.debug("GcodeViewerScreen: d={}".format(d))

                    # handle modal commands
                    if 'G' not in d and ('X' in d or 'Y' in d or 'Z' in d
                                         or 'S' in d):
                        d['G'] = modal_g

                    gcode = int(d['G'])

                    # G92 E0 resets E
                    if 'G' in d and gcode == 92 and 'E' in d:
                        laste = float(d['E'])
                        has_e = True

                    if 'G' in d and (gcode == 91 or gcode == 90):
                        rel_move = gcode == 91

                    # only deal with G0/1/2/3
                    if gcode > 3: continue

                    modal_g = gcode

                    # see if it is 3d printing (ie has an E axis on a G1)
                    if not has_e and ('E' in d and 'G' in d and gcode == 1):
                        has_e = True

                    if rel_move:
                        x += 0 if 'X' not in d else float(d['X'])
                        y += 0 if 'Y' not in d else float(d['Y'])
                        z += 0 if 'Z' not in d else float(d['Z'])

                    else:
                        x = lastpos[0] if 'X' not in d else float(d['X'])
                        y = lastpos[1] if 'Y' not in d else float(d['Y'])
                        z = lastpos[2] if 'Z' not in d else float(d['Z'])

                    i = 0.0 if 'I' not in d else float(d['I'])
                    j = 0.0 if 'J' not in d else float(d['J'])
                    self.rval = 0.0 if 'R' not in d else float(d['R'])

                    e = laste if 'E' not in d else float(d['E'])
                    s = lasts if 'S' not in d else float(d['S'])

                    if not self.twod_mode:
                        # handle layers (when Z changes)
                        if z == -1:
                            # no z seen yet
                            layer = -1
                            continue

                        if lastz is None:
                            # first layer
                            lastz = z
                            layer = 1

                        if z != lastz:
                            # count layers
                            layer += 1
                            lastz = z

                        # wait until we get to the requested layer
                        if layer != target_layer:
                            lastpos[2] = z
                            continue

                        if layer > target_layer and one_layer:
                            # FIXME for some reason this does not work, -- not counting layers
                            #self.last_file_pos= f.tell()
                            #print('Saved position: {}'.format(self.last_file_pos))
                            break

                        self.current_z = z

                    found_layer = True

                    Logger.debug(
                        "GcodeViewerScreen: x= {}, y= {}, z= {}, s= {}".format(
                            x, y, z, s))

                    # find bounding box
                    if math.isnan(min_x) or x < min_x: min_x = x
                    if math.isnan(min_y) or y < min_y: min_y = y
                    if math.isnan(max_x) or x > max_x: max_x = x
                    if math.isnan(max_y) or y > max_y: max_y = y

                    # accumulating vertices is more efficient but we need to flush them at some point
                    # Here we flush them if we encounter a new G code like G3 following G1
                    if last_gcode != gcode:
                        # flush vertices
                        if points:
                            self.canv.add(Color(0, 0, 0))
                            self.canv.add(
                                Line(points=points,
                                     width=1,
                                     cap='none',
                                     joint='none'))
                            points = []

                    last_gcode = gcode

                    # in slicer generated files there is no G0 so we need a way to know when to draw, so if there is an E then draw else don't
                    if gcode == 0:
                        #print("move to: {}, {}, {}".format(x, y, z))
                        # draw moves in dashed red
                        self.canv.add(Color(1, 0, 0))
                        self.canv.add(
                            Line(points=[lastpos[0], lastpos[1], x, y],
                                 width=1,
                                 dash_offset=1,
                                 cap='none',
                                 joint='none'))

                    elif gcode == 1:
                        if ('X' in d or 'Y' in d):
                            if self.laser_mode and s <= 0.01:
                                # do not draw non cutting lines
                                if points:
                                    # draw accumulated points upto this point
                                    self.canv.add(Color(0, 0, 0))
                                    self.canv.add(
                                        Line(points=points,
                                             width=1,
                                             cap='none',
                                             joint='none'))
                                    points = []

                            # for 3d printers (has_e) only draw if there is an E
                            elif not has_e or 'E' in d:
                                # if a CNC gcode file or there is an E in the G1 (3d printing)
                                #print("draw to: {}, {}, {}".format(x, y, z))
                                # collect points but don't draw them yet
                                if len(points) < 2:
                                    points.append(lastpos[0])
                                    points.append(lastpos[1])

                                points.append(x)
                                points.append(y)

                            else:
                                # a G1 with no E, treat as G0 and draw moves in red
                                #print("move to: {}, {}, {}".format(x, y, z))
                                if points:
                                    # draw accumulated points upto this point
                                    self.canv.add(Color(0, 0, 0))
                                    self.canv.add(
                                        Line(points=points,
                                             width=1,
                                             cap='none',
                                             joint='none'))
                                    points = []
                                # now draw the move in red
                                self.canv.add(Color(1, 0, 0))
                                self.canv.add(
                                    Line(points=[lastpos[0], lastpos[1], x, y],
                                         width=1,
                                         cap='none',
                                         joint='none'))

                        else:
                            # A G1 with no X or Y, maybe E only move (retract) or Z move (layer change)
                            if points:
                                # draw accumulated points upto this point
                                self.canv.add(Color(0, 0, 0))
                                self.canv.add(
                                    Line(points=points,
                                         width=1,
                                         cap='none',
                                         joint='none'))
                                points = []

                    elif gcode in [2, 3]:  # CW=2,CCW=3 circle
                        # code cribbed from bCNC
                        xyz = []
                        xyz.append((lastpos[0], lastpos[1], lastpos[2]))
                        uc, vc = self.motionCenter(gcode, plane, lastpos,
                                                   [x, y, z], i, j)

                        if plane == XY:
                            u0 = lastpos[0]
                            v0 = lastpos[1]
                            w0 = lastpos[2]
                            u1 = x
                            v1 = y
                            w1 = z
                        elif plane == XZ:
                            u0 = lastpos[0]
                            v0 = lastpos[2]
                            w0 = lastpos[1]
                            u1 = x
                            v1 = z
                            w1 = y
                            gcode = 5 - gcode  # flip 2-3 when XZ plane is used
                        else:
                            u0 = lastpos[1]
                            v0 = lastpos[2]
                            w0 = lastpos[0]
                            u1 = y
                            v1 = z
                            w1 = x
                        phi0 = math.atan2(v0 - vc, u0 - uc)
                        phi1 = math.atan2(v1 - vc, u1 - uc)
                        try:
                            sagitta = 1.0 - CNC_accuracy / self.rval
                        except ZeroDivisionError:
                            sagitta = 0.0
                        if sagitta > 0.0:
                            df = 2.0 * math.acos(sagitta)
                            df = min(df, math.pi / 4.0)
                        else:
                            df = math.pi / 4.0

                        if gcode == 2:
                            if phi1 >= phi0 - 1e-10: phi1 -= 2.0 * math.pi
                            ws = (w1 - w0) / (phi1 - phi0)
                            phi = phi0 - df
                            while phi > phi1:
                                u = uc + self.rval * math.cos(phi)
                                v = vc + self.rval * math.sin(phi)
                                w = w0 + (phi - phi0) * ws
                                phi -= df
                                if plane == XY:
                                    xyz.append((u, v, w))
                                elif plane == XZ:
                                    xyz.append((u, w, v))
                                else:
                                    xyz.append((w, u, v))
                        else:
                            if phi1 <= phi0 + 1e-10: phi1 += 2.0 * math.pi
                            ws = (w1 - w0) / (phi1 - phi0)
                            phi = phi0 + df
                            while phi < phi1:
                                u = uc + self.rval * math.cos(phi)
                                v = vc + self.rval * math.sin(phi)
                                w = w0 + (phi - phi0) * ws
                                phi += df
                                if plane == XY:
                                    xyz.append((u, v, w))
                                elif plane == XZ:
                                    xyz.append((u, w, v))
                                else:
                                    xyz.append((w, u, v))

                        xyz.append((x, y, z))
                        # plot the points
                        points = []
                        for t in xyz:
                            x1, y1, z1 = t
                            points.append(x1)
                            points.append(y1)
                            max_x = max(x1, max_x)
                            min_x = min(x1, min_x)
                            max_y = max(y1, max_y)
                            min_y = min(y1, min_y)

                        self.canv.add(Color(0, 0, 0))
                        self.canv.add(
                            Line(points=points,
                                 width=1,
                                 cap='none',
                                 joint='none'))
                        points = []

                    # always remember last position
                    lastpos = [x, y, z]
                    laste = e
                    lasts = s

        if not found_layer:
            # we hit the end of file before finding the layer we want
            Logger.info(
                "GcodeViewerScreen: last layer was at {}".format(lastz))
            self.last_target_layer -= 1
            return

        # flush any points not yet drawn
        if points:
            # draw accumulated points upto this point
            self.canv.add(Color(0, 0, 0))
            self.canv.add(
                Line(points=points, width=1, cap='none', joint='none'))
            points = []

        # center the drawing and scale it
        dx = max_x - min_x
        dy = max_y - min_y
        if dx == 0 or dy == 0:
            Logger.warning(
                "GcodeViewerScreen: size is bad, maybe need 2D mode")
            return

        dx += 4
        dy += 4
        Logger.debug("GcodeViewerScreen: dx= {}, dy= {}".format(dx, dy))

        # add in the translation to center object
        self.tx = -min_x - dx / 2
        self.ty = -min_y - dy / 2
        self.canv.insert(1, Translate(self.tx, self.ty))
        Logger.debug("GcodeViewerScreen: tx= {}, ty= {}".format(
            self.tx, self.ty))

        # scale the drawing to fit the screen
        if abs(dx) > abs(dy):
            scale = self.ids.surface.width / abs(dx)
            if abs(dy) * scale > self.ids.surface.height:
                scale *= self.ids.surface.height / (abs(dy) * scale)
        else:
            scale = self.ids.surface.height / abs(dy)
            if abs(dx) * scale > self.ids.surface.width:
                scale *= self.ids.surface.width / (abs(dx) * scale)

        Logger.debug("GcodeViewerScreen: scale= {}".format(scale))
        self.scale = scale
        self.canv.insert(1, Scale(scale))
        # translate to center of canvas
        self.offs = self.ids.surface.center
        self.canv.insert(
            1, Translate(self.ids.surface.center[0],
                         self.ids.surface.center[1]))
        Logger.debug("GcodeViewerScreen: cx= {}, cy= {}".format(
            self.ids.surface.center[0], self.ids.surface.center[1]))
        Logger.debug("GcodeViewerScreen: sx= {}, sy= {}".format(
            self.ids.surface.size[0], self.ids.surface.size[1]))

        # axis Markers
        self.canv.add(Color(0, 1, 0, mode='rgb'))
        self.canv.add(
            Line(points=[0, -10, 0, self.ids.surface.height / scale],
                 width=1,
                 cap='none',
                 joint='none'))
        self.canv.add(
            Line(points=[-10, 0, self.ids.surface.width / scale, 0],
                 width=1,
                 cap='none',
                 joint='none'))

        # tool position marker
        if self.app.is_connected:
            x = self.app.wpos[0]
            y = self.app.wpos[1]
            r = (10.0 / self.ids.surface.scale) / scale
            self.canv.add(Color(1, 0, 0, mode='rgb', group="tool"))
            self.canv.add(Line(circle=(x, y, r), group="tool"))

        # self.canv.add(Rectangle(pos=(x, y-r/2), size=(1/scale, r), group="tool"))
        # self.canv.add(Rectangle(pos=(x-r/2, y), size=(r, 1/scale), group="tool"))

        self.canv.add(PopMatrix())
        self._loaded_ok = True
        Logger.debug("GcodeViewerScreen: done loading")

    def update_tool(self, i, v):
        if not self.is_visible or not self.app.is_connected: return

        # follow the tool path
        #self.canv.remove_group("tool")
        x = v[0]
        y = v[1]
        r = (10.0 / self.ids.surface.scale) / self.scale
        g = self.canv.get_group("tool")
        if g:
            g[2].circle = (x, y, r)
            # g[4].pos= x, y-r/2
            # g[6].pos= x-r/2, y

    def transform_to_wpos(self, posx, posy):
        ''' convert touch coords to local scatter widget coords, relative to lower bottom corner '''
        pos = self.ids.surface.to_widget(posx, posy)
        # convert to original model coordinates (mm), need to take into account scale and translate
        wpos = ((pos[0] - self.offs[0]) / self.scale - self.tx,
                (pos[1] - self.offs[1]) / self.scale - self.ty)
        return wpos

    def transform_to_spos(self, posx, posy):
        ''' inverse transform of model coordinates to scatter coordinates '''
        pos = ((((posx + self.tx) * self.scale) + self.offs[0]),
               (((posy + self.ty) * self.scale) + self.offs[1]))
        spos = self.ids.surface.to_window(*pos)
        #print("pos= {}, spos= {}".format(pos, spos))
        return spos

    def moved(self, w, touch):
        # we scaled or moved the scatter so need to reposition cursor
        # TODO it would be nice if the cursor stayed where it was relative to the model during a move or scale
        # NOTE right now we can't move or scale while cursor is on
        # if self.select_mode:
        #     x, y= (self.crossx[0].pos[0], self.crossx[1].pos[1])
        #     self.stop_cursor(x, y)
        #     self.start_cursor(x, y)

        # hide tool marker
        self.canv.remove_group('tool')

    def start_cursor(self, x, y):
        tx, ty = self.transform_to_wpos(x, y)
        label = CoreLabel(text="{:1.2f},{:1.2f}".format(tx, ty))
        label.refresh()
        texture = label.texture
        px, py = (x, y)
        with self.ids.surface.canvas.after:
            Color(0, 0, 1, mode='rgb', group='cursor_group')
            self.crossx = [
                Rectangle(pos=(px, 0),
                          size=(1, self.height),
                          group='cursor_group'),
                Rectangle(pos=(0, py),
                          size=(self.width, 1),
                          group='cursor_group'),
                Line(circle=(px, py, 20), group='cursor_group'),
                Rectangle(texture=texture,
                          pos=(px - texture.size[0] / 2, py - 40),
                          size=texture.size,
                          group='cursor_group')
            ]

    def move_cursor_by(self, dx, dy):
        x, y = (self.crossx[0].pos[0] + dx, self.crossx[1].pos[1] + dy)

        self.crossx[0].pos = x, 0
        self.crossx[1].pos = 0, y
        self.crossx[2].circle = (x, y, 20)
        tx, ty = self.transform_to_wpos(x, y)
        label = CoreLabel(text="{:1.2f},{:1.2f}".format(tx, ty))
        label.refresh()
        texture = label.texture
        self.crossx[3].texture = texture
        self.crossx[3].pos = x - texture.size[0] / 2, y - 40

    def stop_cursor(self, x=0, y=0):
        self.ids.surface.canvas.after.remove_group('cursor_group')
        self.crossx = None

    def on_touch_down(self, touch):
        #print(self.ids.surface.bbox)
        if self.ids.view_window.collide_point(touch.x, touch.y):
            # if within the scatter window
            if self.select_mode:
                touch.grab(self)
                return True

            elif touch.is_mouse_scrolling:
                # Allow mouse scroll wheel to zoom in/out
                if touch.button == 'scrolldown':
                    # zoom in
                    if self.ids.surface.scale < 100:
                        rescale = 1.1
                        self.ids.surface.apply_transform(
                            Matrix().scale(rescale, rescale, rescale),
                            post_multiply=True,
                            anchor=self.ids.surface.to_widget(*touch.pos))

                elif touch.button == 'scrollup':
                    # zoom out
                    if self.ids.surface.scale > 0.01:
                        rescale = 0.8
                        self.ids.surface.apply_transform(
                            Matrix().scale(rescale, rescale, rescale),
                            post_multiply=True,
                            anchor=self.ids.surface.to_widget(*touch.pos))

                self.moved(None, touch)
                return True

        return super(GcodeViewerScreen, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        if self.select_mode:
            if touch.grab_current is not self:
                return False

            dx = touch.dpos[0]
            dy = touch.dpos[1]
            self.move_cursor_by(dx, dy)
            return True

        else:
            return super(GcodeViewerScreen, self).on_touch_move(touch)

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            touch.ungrab(self)
            return True

        return super(GcodeViewerScreen, self).on_touch_up(touch)

    def select(self, on):
        if not on and self.select_mode:
            self.stop_cursor()
            self.select_mode = False
        elif on and not self.select_mode:
            x, y = self.center
            self.start_cursor(x, y)
            self.select_mode = True

    def move_gantry(self):
        if not self.select_mode:
            return

        self.select_mode = False
        self.ids.select_mode_but.state = 'normal'

        # convert to original model coordinates (mm), need to take into account scale and translate
        x, y = (self.crossx[0].pos[0], self.crossx[1].pos[1])
        self.stop_cursor(x, y)
        wpos = self.transform_to_wpos(x, y)

        if self.comms:
            self.comms.write('G0 X{:1.2f} Y{:1.2f}\n'.format(wpos[0], wpos[1]))
        else:
            print('Move Gantry to: {:1.2f}, {:1.2f}'.format(wpos[0], wpos[1]))
            print('G0 X{:1.2f} Y{:1.2f}'.format(wpos[0], wpos[1]))

    def set_wcs(self):
        if not self.select_mode:
            return

        self.select_mode = False
        self.ids.select_mode_but.state = 'normal'

        # convert to original model coordinates (mm), need to take into account scale and translate
        x, y = (self.crossx[0].pos[0], self.crossx[1].pos[1])
        self.stop_cursor(x, y)
        wpos = self.transform_to_wpos(x, y)
        if self.comms:
            self.comms.write('G10 L20 P0 X{:1.2f} Y{:1.2f}\n'.format(
                wpos[0], wpos[1]))
        else:
            print('Set WCS to: {:1.2f}, {:1.2f}'.format(wpos[0], wpos[1]))
            print('G10 L20 P0 X{:1.2f} Y{:1.2f}'.format(wpos[0], wpos[1]))

    def set_type(self, t):
        if t == '3D':
            self.twod_mode = False
            self.laser_mode = False
        elif t == '2D':
            self.twod_mode = True
            self.laser_mode = False
        elif t == 'Laser':
            self.twod_mode = True
            self.laser_mode = True

        self.loading(0 if self.twod_mode else 1)
Пример #2
0
class DisplayController(object):
    def __init__(self, width, height, canvas, ac, eye_pos, eye_angle):
        super(DisplayController, self).__init__()
        self.width = width
        self.height = height
        self.canvas = canvas
        self.eye_pos = eye_pos
        self.eye_angle = eye_angle
        self.ac = ac

        self.canvas.shader.source = resource_find('data/simple.glsl')

        self.all_notes = []
        self.future_notes = {}
        self.past_notes = {}
        self.ticks = []

        # self.planes = range(0, 10 * config['PLANE_SPACING'], config['PLANE_SPACING'])
        self.planes = []
        self.lines = []

        self.past_displays = InstructionGroup()
        self.future_displays = InstructionGroup()
        self.plane_displays = InstructionGroup()
        self.line_displays = InstructionGroup()

        self.fixed_x = Translate(0, 0, 0)
        self.fixed_y = Translate(0, 0, 0)
        self.fixed_z = Translate(0, 0, 0)
        self.fixed_azi = Rotate(origin=(0, 0, 0), axis=(0, 1, 0))
        self.fixed_ele = Rotate(origin=(0, 0, 0), axis=(1, 0, 0))

        self.alpha_callback = Callback(self.alpha_sample_callback)
        self.disable_alpha_callback = Callback(
            self.disable_alpha_sample_callback)
        self.alpha_instruction = InstructionGroup()
        self.alpha_disable_instruction = InstructionGroup()
        self.alpha_sample_enable = False

        self.canvas.add(Callback(self.setup_gl_context))
        self.canvas.add(self.alpha_instruction)
        self.canvas.add(PushMatrix())
        self.canvas.add(UpdateNormalMatrix())

        self.canvas.add(PushMatrix())
        self.canvas.add(self.fixed_z)
        self.canvas.add(self.line_displays)
        self.canvas.add(PopMatrix())

        self.canvas.add(PushMatrix())
        self.canvas.add(self.past_displays)
        self.canvas.add(self.future_displays)
        self.canvas.add(PopMatrix())

        self.canvas.add(PushMatrix())
        # self.canvas.add(self.fixed_x)
        # self.canvas.add(self.fixed_y)
        self.canvas.add(self.plane_displays)
        self.canvas.add(PopMatrix())

        # self.canvas.add(PushMatrix())
        # self.canvas.add(self.fixed_x)
        # self.canvas.add(self.fixed_y)
        # self.canvas.add(self.fixed_z)
        # self.canvas.add(Plane(-config['SELF_PLANE_DISTANCE'], size=0.1, color=(0x20/255., 0xD8/255., 0xE9/255.), tr=0.2))
        # self.canvas.add(PopMatrix())

        self.canvas.add(PopMatrix())
        self.canvas.add(self.alpha_disable_instruction)
        self.canvas.add(Callback(self.reset_gl_context))

        self.draw_planes()

    def add_notes(self, note_data):
        s = config['LINE_SPACING']
        for nd in note_data:
            if (nd.x, nd.y) not in self.lines and float(
                    nd.x).is_integer() and float(nd.y).is_integer():
                self.line_displays.add(
                    Line(nd.x * s, nd.y * s, color=(0.7, 0.5, 0.0)))
                self.lines.append((nd.x, nd.y))

        self.all_notes.extend(note_data)
        self.all_notes.sort(key=lambda n: n.tick)
        self.ticks = map(lambda n: n.tick, self.all_notes)

    def draw_planes(self):
        for p in self.planes:
            self.plane_displays.add(
                Plane(p, color=(0xE9 / 255., 0xD8 / 255., 0x3C / 255.)))

    def setup_gl_context(self, *args):
        gl.glEnable(gl.GL_DEPTH_TEST)

    def toggle_alpha_sample(self):
        if self.alpha_sample_enable:
            self.alpha_instruction.remove(self.alpha_callback)
            self.alpha_disable_instruction.remove(self.disable_alpha_callback)
            self.alpha_sample_enable = False
        else:
            self.alpha_instruction.add(self.alpha_callback)
            self.alpha_disable_instruction.add(self.disable_alpha_callback)
            self.alpha_sample_enable = True

    def alpha_sample_callback(self, *args):
        gl.glEnable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE)

    def reset_gl_context(self, *args):
        gl.glDisable(gl.GL_DEPTH_TEST)

    def disable_alpha_sample_callback(self, *args):
        gl.glDisable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE)

    def get_look_at(self, x, y, z, azi, ele):
        dx = -np.sin(azi) * np.cos(ele)
        dy = np.sin(ele)
        dz = -np.cos(azi) * np.cos(ele)

        # Not sure why up has to just be up...
        upx, upy, upz = (0, 1, 0)

        mat = Matrix()
        mat = mat.look_at(x, y, z, x + dx, y + dy, z + dz, upx, upy, upz)
        return mat

    def update_camera(self, pos, angle):
        self.eye_pos = pos
        self.eye_angle = angle
        x, y, z = pos
        azi, ele = angle
        asp = self.width / float(self.height)
        mat = self.get_look_at(x, y, z, azi, ele)

        proj = Matrix()
        proj.perspective(30, asp, 1, 100)

        self.canvas['projection_mat'] = proj
        self.canvas['modelview_mat'] = mat

        self.fixed_x.x = x
        self.fixed_y.y = y
        self.fixed_z.z = z
        self.fixed_azi.angle = azi * 180 / np.pi
        self.fixed_ele.angle = ele * 180 / np.pi

    def get_notes_in_range(self, start_tick, end_tick):
        l = bisect.bisect_left(self.ticks, start_tick)
        r = bisect.bisect_left(self.ticks, end_tick)
        if r <= 0:
            return []
        return self.all_notes[l:r]

    def on_update(self, tick):
        self_plane_z = self.eye_pos[2] - config['SELF_PLANE_DISTANCE']

        eye_tick = tick + (-self.eye_pos[2] / config['UNITS_PER_TICK'])
        dt = kivyClock.frametime

        future_range = self.get_notes_in_range(
            eye_tick, eye_tick + config['VISIBLE_TICK_RANGE'])
        past_range = self.get_notes_in_range(
            eye_tick - config['VISIBLE_TICK_RANGE'], eye_tick)

        # COMPLEX LOGIC TO MAINTAIN LISTS OF VISIBLE NOTES ORDERED BY DISTANCE FROM CAMERA
        # far future <-> future
        # future <-> past
        # past <-> far past

        # far future -> future
        fftof = list(
            x for x in future_range
            if x not in self.past_notes and x not in self.future_notes)
        # future -> far future
        ftoff = list(x for x in self.future_notes
                     if x not in future_range and x not in past_range)
        # future -> past
        ftop = list(x for x in past_range if x in self.future_notes)
        # past -> future
        ptof = list(x for x in future_range if x in self.past_notes)
        # past -> far past
        ptofp = list(x for x in self.past_notes
                     if x not in future_range and x not in past_range)
        # far past -> past
        fptop = list(
            x for x in past_range
            if x not in self.past_notes and x not in self.future_notes)

        # handle ff -> f
        for nd in sorted(fftof, key=lambda n: n.tick):
            ndisp = NoteDisplay(nd, self.planes, self.ac)
            self.future_displays.insert(0, ndisp)
            self.future_notes[nd] = ndisp

        # handle f -> ff
        for nd in ftoff:
            ndisp = self.future_notes[nd]
            self.future_displays.remove(ndisp)
            del self.future_notes[nd]

        # handle f -> p
        for nd in sorted(ftop, key=lambda n: n.tick):
            ndisp = self.future_notes[nd]
            self.future_displays.remove(ndisp)
            self.past_displays.add(ndisp)
            self.past_notes[nd] = ndisp
            del self.future_notes[nd]

        # handle p -> f
        for nd in sorted(ptof, key=lambda n: -n.tick):
            ndisp = self.past_notes[nd]
            self.past_displays.remove(ndisp)
            self.future_displays.add(ndisp)
            self.future_notes[nd] = ndisp
            del self.past_notes[nd]

        # handle p -> fp
        for nd in ptofp:
            ndisp = self.past_notes[nd]
            self.past_displays.remove(ndisp)
            del self.past_notes[nd]

        # handle fp -> p
        for nd in sorted(fptop, key=lambda n: -n.tick):
            ndisp = NoteDisplay(nd, self.planes, self.ac)
            self.past_displays.insert(0, ndisp)
            self.past_notes[nd] = ndisp

        for s in self.future_notes.values() + self.past_notes.values():
            pos = s.pos_from_tick(tick)
            s.set_pos(pos)
            s.on_update(dt, eye_tick, self.eye_angle)

            if s.past_me and pos[
                    2] < self_plane_z - 0.5 * config['SELF_PLANE_DISTANCE']:
                s.past_me = False
            if pos[2] > self_plane_z and not s.past_me:
                s.sound(tick, pos)
                s.past_me = True

        Logger.debug('Number of notes: %s' %
                     (len(self.future_notes) + len(self.past_notes)))
Пример #3
0
class DisplayController(object):
    def __init__(self, width, height, canvas, ac, eye_pos, eye_angle):
        super(DisplayController, self).__init__()
        self.width = width
        self.height = height
        self.canvas = canvas
        self.eye_pos = eye_pos
        self.eye_angle = eye_angle
        self.ac = ac

        self.canvas.shader.source = resource_find('data/simple.glsl')

        self.all_notes = []
        self.future_notes = {}
        self.past_notes = {}
        self.ticks = []

        # self.planes = range(0, 10 * config['PLANE_SPACING'], config['PLANE_SPACING'])
        self.planes = []
        self.lines = []

        self.past_displays = InstructionGroup()
        self.future_displays = InstructionGroup()
        self.plane_displays = InstructionGroup()
        self.line_displays = InstructionGroup()

        self.fixed_x = Translate(0, 0, 0)
        self.fixed_y = Translate(0, 0, 0)
        self.fixed_z = Translate(0, 0, 0)
        self.fixed_azi = Rotate(origin=(0, 0, 0), axis=(0, 1, 0))
        self.fixed_ele = Rotate(origin=(0, 0, 0), axis=(1, 0, 0))

        self.alpha_callback = Callback(self.alpha_sample_callback)
        self.disable_alpha_callback = Callback(self.disable_alpha_sample_callback)
        self.alpha_instruction = InstructionGroup()
        self.alpha_disable_instruction = InstructionGroup()
        self.alpha_sample_enable = False

        self.canvas.add(Callback(self.setup_gl_context))
        self.canvas.add(self.alpha_instruction)
        self.canvas.add(PushMatrix())
        self.canvas.add(UpdateNormalMatrix())

        
        
        self.canvas.add(PushMatrix())
        self.canvas.add(self.fixed_z)
        self.canvas.add(self.line_displays)
        self.canvas.add(PopMatrix())

        self.canvas.add(PushMatrix())
        self.canvas.add(self.past_displays)
        self.canvas.add(self.future_displays)
        self.canvas.add(PopMatrix())

        self.canvas.add(PushMatrix())
        # self.canvas.add(self.fixed_x)
        # self.canvas.add(self.fixed_y)
        self.canvas.add(self.plane_displays)
        self.canvas.add(PopMatrix())

        # self.canvas.add(PushMatrix())
        # self.canvas.add(self.fixed_x)
        # self.canvas.add(self.fixed_y)
        # self.canvas.add(self.fixed_z)
        # self.canvas.add(Plane(-config['SELF_PLANE_DISTANCE'], size=0.1, color=(0x20/255., 0xD8/255., 0xE9/255.), tr=0.2))
        # self.canvas.add(PopMatrix())

        self.canvas.add(PopMatrix())
        self.canvas.add(self.alpha_disable_instruction)
        self.canvas.add(Callback(self.reset_gl_context))

        self.draw_planes()

    def add_notes(self, note_data):
        s = config['LINE_SPACING']
        for nd in note_data:
            if (nd.x, nd.y) not in self.lines and float(nd.x).is_integer() and float(nd.y).is_integer():
                self.line_displays.add(Line(nd.x * s, nd.y * s, color=(0.7, 0.5, 0.0)))
                self.lines.append((nd.x, nd.y))

        self.all_notes.extend(note_data)
        self.all_notes.sort(key=lambda n:n.tick)
        self.ticks = map(lambda n:n.tick, self.all_notes)

    def draw_planes(self):
        for p in self.planes:
            self.plane_displays.add(Plane(p, color=(0xE9/255., 0xD8/255., 0x3C/255.)))

    def setup_gl_context(self, *args):
        gl.glEnable(gl.GL_DEPTH_TEST)

    def toggle_alpha_sample(self):
        if self.alpha_sample_enable:
            self.alpha_instruction.remove(self.alpha_callback)
            self.alpha_disable_instruction.remove(self.disable_alpha_callback)
            self.alpha_sample_enable = False
        else:
            self.alpha_instruction.add(self.alpha_callback)
            self.alpha_disable_instruction.add(self.disable_alpha_callback)
            self.alpha_sample_enable = True

    def alpha_sample_callback(self, *args):
        gl.glEnable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE)

    def reset_gl_context(self, *args):
        gl.glDisable(gl.GL_DEPTH_TEST)

    def disable_alpha_sample_callback(self, *args):
        gl.glDisable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE)

    def get_look_at(self, x, y, z, azi, ele):
        dx = - np.sin(azi) * np.cos(ele)
        dy = np.sin(ele)
        dz = - np.cos(azi) * np.cos(ele)

        # Not sure why up has to just be up...
        upx, upy, upz = (0, 1, 0)

        mat = Matrix()
        mat = mat.look_at(x, y, z,
                          x + dx, y + dy, z + dz,
                          upx, upy, upz)
        return mat

    def update_camera(self, pos, angle):
        self.eye_pos = pos
        self.eye_angle = angle
        x, y, z = pos
        azi, ele = angle
        asp = self.width / float(self.height)
        mat = self.get_look_at(x, y, z, azi, ele)

        proj = Matrix()
        proj.perspective(30, asp, 1, 100)

        self.canvas['projection_mat'] = proj
        self.canvas['modelview_mat'] = mat

        self.fixed_x.x = x
        self.fixed_y.y = y
        self.fixed_z.z = z
        self.fixed_azi.angle = azi * 180/np.pi
        self.fixed_ele.angle = ele * 180/np.pi

    def get_notes_in_range(self, start_tick, end_tick):
      l = bisect.bisect_left(self.ticks, start_tick)
      r = bisect.bisect_left(self.ticks, end_tick)
      if r <= 0:
         return []
      return self.all_notes[l:r]

    def on_update(self, tick):
        self_plane_z = self.eye_pos[2] - config['SELF_PLANE_DISTANCE']

        eye_tick = tick + ( - self.eye_pos[2] / config['UNITS_PER_TICK'])
        dt = kivyClock.frametime

        future_range = self.get_notes_in_range(eye_tick, eye_tick + config['VISIBLE_TICK_RANGE'])
        past_range = self.get_notes_in_range(eye_tick - config['VISIBLE_TICK_RANGE'], eye_tick)

        # COMPLEX LOGIC TO MAINTAIN LISTS OF VISIBLE NOTES ORDERED BY DISTANCE FROM CAMERA
        # far future <-> future
        # future <-> past
        # past <-> far past

        # far future -> future
        fftof = list(x for x in future_range if x not in self.past_notes and x not in self.future_notes)
        # future -> far future
        ftoff = list(x for x in self.future_notes if x not in future_range and x not in past_range)
        # future -> past
        ftop = list(x for x in past_range if x in self.future_notes)
        # past -> future
        ptof = list(x for x in future_range if x in self.past_notes)
        # past -> far past
        ptofp = list(x for x in self.past_notes if x not in future_range and x not in past_range)
        # far past -> past
        fptop = list(x for x in past_range if x not in self.past_notes and x not in self.future_notes)

        # handle ff -> f
        for nd in sorted(fftof, key=lambda n: n.tick):
            ndisp = NoteDisplay(nd, self.planes, self.ac)
            self.future_displays.insert(0, ndisp)
            self.future_notes[nd] = ndisp

        # handle f -> ff
        for nd in ftoff:
            ndisp = self.future_notes[nd]
            self.future_displays.remove(ndisp)
            del self.future_notes[nd]

        # handle f -> p
        for nd in sorted(ftop, key=lambda n: n.tick):
            ndisp = self.future_notes[nd]
            self.future_displays.remove(ndisp)
            self.past_displays.add(ndisp)
            self.past_notes[nd] = ndisp
            del self.future_notes[nd]

        # handle p -> f
        for nd in sorted(ptof, key=lambda n: -n.tick):
            ndisp = self.past_notes[nd]
            self.past_displays.remove(ndisp)
            self.future_displays.add(ndisp)
            self.future_notes[nd] = ndisp
            del self.past_notes[nd]

        # handle p -> fp
        for nd in ptofp:
            ndisp = self.past_notes[nd]
            self.past_displays.remove(ndisp)
            del self.past_notes[nd]

        # handle fp -> p
        for nd in sorted(fptop, key=lambda n: -n.tick):
            ndisp = NoteDisplay(nd, self.planes, self.ac)
            self.past_displays.insert(0, ndisp)
            self.past_notes[nd] = ndisp


        for s in self.future_notes.values() + self.past_notes.values():
            pos = s.pos_from_tick(tick)
            s.set_pos(pos)
            s.on_update(dt, eye_tick, self.eye_angle)

            if s.past_me and pos[2] < self_plane_z - 0.5 * config['SELF_PLANE_DISTANCE']:
                s.past_me = False
            if pos[2] > self_plane_z and not s.past_me:
                s.sound(tick, pos)
                s.past_me = True


        Logger.debug('Number of notes: %s' % (len(self.future_notes) + len(self.past_notes)))