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