Ejemplo n.º 1
0
class BillboardDisplay(InstructionGroup):
    def __init__(self, pos, texture, size_x=1.0, size_y=1.0, intensity=1.0, Tr=1.0):
        super(BillboardDisplay, self).__init__()
        
        self.intensity = intensity
        self.Tr = Tr

        self.translate = Translate()
        self.rotate_azi = Rotate(origin=(0,0,0), axis=(0,1,0))
        self.rotate_ele = Rotate(origin=(0,0,0), axis=(1,0,0))
        self.scale = Scale()
        self.color_instruction = InstructionGroup()
        self.mesh = Mesh(
            texture=texture,
            vertices=rectangle_nurb.vertices,
            indices=rectangle_nurb.indices,
            fmt=rectangle_nurb.vertex_format,
            mode='triangles'
        )

        self.add(PushMatrix())
        self.add(self.translate)
        self.add(self.rotate_azi)
        self.add(self.rotate_ele)
        self.add(self.scale)
        self.add(self.color_instruction)
        self.add(self.mesh)
        self.add(PopMatrix())

        self.set_color(intensity=intensity, Tr=Tr)
        self.set_size(size_x, size_y)
        self.set_pos(pos)
        self.set_texture(texture)

    def set_texture(self, texture):
        self.texture = texture
        self.mesh.texture = texture

    def set_rotate(self, angles):
        self.rotate_azi.angle = angles[0] * 180/np.pi
        self.rotate_ele.angle = angles[1] * 180/np.pi

    def set_size(self, size_x, size_y):
        self.scale.xyz = (size_x, size_y, 1)

    def set_pos(self, pos):
        self.translate.xyz = pos

    def set_color(self, intensity=1.0, Tr=1.0):
        self.color_instruction.clear()
        self.color_instruction.add(
            ChangeState(
                Kd=(0.6, 0.6, 0.6),
                Ka=(0.8, 0.8, 0.8),
                Ks=(0.9, 0.9, 0.9),
                Tr=Tr,
                Ns=1.0,
                intensity=intensity
            )
        )
Ejemplo n.º 2
0
class NurbDisplay(InstructionGroup):
    def __init__(self,
                 nurb,
                 pos,
                 size=1.0,
                 color=(0.0, 1.0, 0.0),
                 tr=1.0,
                 intensity=1.0):
        super(NurbDisplay, self).__init__()

        self.color = color
        self.tr = tr
        self.intensity = intensity

        self.translate = Translate()
        self.scale = Scale()
        self.color_instruction = InstructionGroup()

        self.add(PushMatrix())
        self.add(self.translate)
        self.add(self.scale)
        self.add(self.color_instruction)
        self.add(self.make_mesh(nurb))
        self.add(PopMatrix())

        self.set_color()
        self.set_size(size)
        self.set_pos(pos)

    def make_mesh(self, m):
        return Mesh(vertices=m.vertices,
                    indices=m.indices,
                    fmt=m.vertex_format,
                    mode='triangles')

    def set_size(self, size):
        self.scale.xyz = (size, size, size)

    def set_pos(self, pos):
        self.translate.xyz = pos

    def set_color(self, color=None, tr=None, intensity=None):
        if tr is not None:
            self.tr = tr
        if intensity is not None:
            self.intensity = intensity
        if color is not None:
            self.color = color

        self.color_instruction.clear()
        self.color_instruction.add(
            ChangeState(Kd=self.color,
                        Ka=self.color,
                        Ks=(0.3, 0.3, 0.3),
                        Tr=self.tr,
                        Ns=1.0,
                        intensity=self.intensity))
Ejemplo n.º 3
0
class NurbDisplay(InstructionGroup):
    def __init__(self, nurb, pos, size=1.0, color=(0.0,1.0,0.0), tr=1.0, intensity=1.0):
        super(NurbDisplay, self).__init__()
        
        self.color = color
        self.tr = tr
        self.intensity = intensity

        self.translate = Translate()
        self.scale = Scale()
        self.color_instruction = InstructionGroup()

        self.add(PushMatrix())
        self.add(self.translate)
        self.add(self.scale)
        self.add(self.color_instruction)
        self.add(self.make_mesh(nurb))
        self.add(PopMatrix())

        self.set_color()
        self.set_size(size)
        self.set_pos(pos)

    def make_mesh(self, m):
        return Mesh(
            vertices=m.vertices,
            indices=m.indices,
            fmt=m.vertex_format,
            mode='triangles'
        )

    def set_size(self, size):
        self.scale.xyz = (size, size, size)

    def set_pos(self, pos):
        self.translate.xyz = pos

    def set_color(self, color=None, tr=None, intensity=None):
        if tr is not None:
            self.tr = tr
        if intensity is not None:
            self.intensity = intensity
        if color is not None:
            self.color = color

        self.color_instruction.clear()
        self.color_instruction.add(
            ChangeState(
                Kd=self.color,
                Ka=self.color,
                Ks=(0.3, 0.3, 0.3),
                Tr=self.tr,
                Ns=1.0,
                intensity=self.intensity
            )
        )
Ejemplo n.º 4
0
class BackgroundLabel(Label):
    def __init__(self, **kwargs):
        super(BackgroundLabel, self).__init__(**kwargs)
        self.IG = InstructionGroup()
        self.background_color = (0, 0, 0, 1)
        self.color = (1, 1, 1, 1)

    def Set_Background_Color(self):
        self.opacity = 1
        self.IG.clear()
        self.IG.add(Color(rgba=(self.background_color)))
        self.IG.add(Rectangle(size=(self.size), pos=(self.pos)))
        self.canvas.before.add(self.IG)
Ejemplo n.º 5
0
class LinePlot(Widget):

    viewport = ListProperty(None)
    line_width = NumericProperty(5.)
    line_color = ListProperty([0,.8,1])
    border_width = NumericProperty(5)
    border_color = ListProperty([.3,.3,.3])
    flattened_points = ListProperty(None)
    tick_distance_x = NumericProperty(None)
    tick_distance_y = NumericProperty(None)
    tick_color = ListProperty([.3,.3,.3])
    select_circle = ListProperty([0,0,0])

    def __init__(self, points, **kwargs):
        # calculate some basic information about the data
        self.points = points
        self.points_x = zip(*points)[0]
        self.points_y = zip(*points)[1]

        # if we could figure out how to draw an arbitrary number of ticks in kv, this would be cleaner
        self.ticks = InstructionGroup()
        self.tick_translate = Translate()
        

        super(LinePlot, self).__init__(**kwargs)
        self.canvas.insert(0, self.ticks)
        self.viewport = (min(self.points_x), min(self.points_y), max(self.points_x), max(self.points_y))
    
    # recalculate viewport when size changes
    def on_size(self, instance, value):
        self.on_viewport(None, self.viewport)

    def on_pos(self, instance, value):
        self.tick_translate.xy = self.x, self.y
        
    def on_viewport(self, instance, value):
        if value is None or len(value) != 4: return
        print value
        self.vp_width_convert = float(self.width)/(value[2] - value[0])
        self.vp_height_convert = float(self.height)/(value[3] - value[1])

        # calculate the actual display points based on the viewport and self.size
        self.display_points = [self.to_display_point(*p) for p in self.points if value[0] <= p[0] <= value[2] and value[1] <= p[1] <= value[3]]
        self.flattened_points = [item for sublist in self.display_points for item in sublist]
        self.draw_ticks()

    # it would be real nice if we could figure out how to do this in kv
    def draw_ticks(self):
        self.ticks.clear()
        self.ticks.add(Color(*self.tick_color, mode='rgb'))
        self.ticks.add(PushMatrix())
        self.ticks.add(self.tick_translate)
        if self.tick_distance_x is not None:
            first_x_tick = self.tick_distance_x*(int(self.viewport[0]/self.tick_distance_x) + 1)
            for x in drange(first_x_tick, self.viewport[2], self.tick_distance_x):
                start = self.to_display_point(x, self.viewport[1])
                stop = self.to_display_point(x, self.viewport[3])
                self.ticks.add(Line(points=[start[0], start[1], stop[0], stop[1]]))

        if self.tick_distance_y is not None:
            first_y_tick = self.tick_distance_y*(int(self.viewport[1]/self.tick_distance_y) + 1)
            for y in drange(first_y_tick, self.viewport[3], self.tick_distance_y):
                start = self.to_display_point(self.viewport[0], y)
                stop = self.to_display_point(self.viewport[2], y)
                self.ticks.add(Line(points=[start[0], start[1], stop[0], stop[1]]))
        
        self.ticks.add(PopMatrix())

    def to_display_point(self, x, y):
        return (self.vp_width_convert*(x-self.viewport[0]), self.vp_height_convert*(y-self.viewport[1]))

    def select_point(self, x, y):
        # get point from self.displaypoints that is closest to x
        
        distances = [abs((x - self.x) - d[0]) for d in self.display_points]
        idx = min(xrange(len(distances)),key=distances.__getitem__)
        self.select_circle = [self.display_points[idx][0], self.display_points[idx][1], 20]
        return self.points[idx]




    def to_xy(self, x, y):
        xt = (x - self.x)/self.vp_width_convert+self.viewport[0]
        yt = (y - self.y)/self.vp_height_convert+self.viewport[1]
        return (xt, yt)
Ejemplo n.º 6
0
class VisualEditor(FloatLayout):
    grid_lay = ObjectProperty(None)
    snippet_area = ObjectProperty(None)
    connections = ListProperty([])

    def __init__(self, **kwargs):
        global app

        Clock.schedule_once(self.init_grid_lay, 1)
        Clock.schedule_interval(self.update, 1 / 30)
        super(VisualEditor, self).__init__(**kwargs)
        Window.bind(on_motion=self.on_motion)

        self.connection_starter = None
        self.connection_ender = None

        app.editor = self

        self.line_group = None

    def get_state(self):
        return (self.connections, self.snippet_area.children)

    def update(self, dt):
        if self.snippet_area and self.line_group is None:
            self.line_group = InstructionGroup()
            self.snippet_area.canvas.add(self.line_group)

        if self.line_group:
            if len(self.connections) > 0:
                self.line_group.clear()
                for conn in self.connections:
                    self.line_group.add(Color(*conn.get_color()))
                    self.line_group.add(
                        Line(points=(conn.input.get_pos() +
                                     conn.output.get_pos()),
                             width=1))
            else:
                self.line_group.clear()

    def try_to_make_connection(self):
        if self.connection_starter and self.connection_ender and \
                self.connection_starter.conn_type == self.connection_ender.conn_type and \
                self.connection_starter.parent is not self.connection_ender.parent:

            for conn in self.connections:
                if conn.input == self.connection_starter and conn.output == self.connection_ender:
                    break
            else:
                self.connections.append(
                    Connection(self.connection_starter, self.connection_ender,
                               self.connection_ender.conn_type))

            self.connection_starter = None
            self.connection_ender = None

    def on_motion(self, etype, stm, touch):
        mult = 0

        if touch.is_mouse_scrolling:
            if touch.button == 'scrolldown':
                mult = 0.1
            if touch.button == 'scrollup':
                mult = -0.1
        else:
            return

        if self.collide_point(touch.x, touch.y):
            for child in self.snippet_area.children:
                if child.collide_point(touch.x, touch.y):
                    break
            else:
                if touch.is_mouse_scrolling:
                    for child in self.snippet_area.children:
                        tv = qvec.VecNd(tuple(child.pos)) - \
                            qvec.VecNd(tuple(touch.pos))
                        tv *= mult
                        child.pos = (qvec.VecNd(child.pos) + tv).to_tuple()

    def on_touch_down(self, touch):
        if super().on_touch_down(touch):
            return True

        if touch.button == MOVE_BUTTON and self.collide_point(
                touch.x, touch.y):
            for child in self.snippet_area.children:
                if child.collide_point(touch.x, touch.y):
                    break
            else:
                touch.grab(self)
                return True

        return super().on_touch_down(touch)

    def on_touch_move(self, touch):
        if super().on_touch_move(touch):
            return True

        if touch.button == MOVE_BUTTON:
            if touch.grab_current is self:
                for child in self.snippet_area.children:
                    child.pos[0] += touch.dx
                    child.pos[1] += touch.dy
                return True

    def on_touch_up(self, touch):
        if super().on_touch_up(touch):
            return True

        if touch.button == MOVE_BUTTON and self.collide_point(
                touch.x, touch.y):
            if touch.grab_current is self:
                touch.ungrab(self)
                return True

    def button_factory(self, snippet):
        def f(*args):
            snp = snippet()
            snp.editor = self
            self.snippet_area.add_widget(snp)
            snp.pos = (self.snippet_area.height / 2,
                       self.snippet_area.width / 2)

        b = Button(valign='middle',
                   text_size=(100, 40),
                   size_hint_y=None,
                   height=40,
                   text=l18n.get(snippet.get_snippet_name()))
        b.bind(on_press=f)
        return b

    def init_grid_lay(self, *args):
        global snippets
        for snippet in snippets:
            self.add_child_to_grid_lay(self.button_factory(snippet))

    def add_child_to_grid_lay(self, child):
        if self.grid_lay:
            self.grid_lay.add_widget(child)

            target_height = 0

            for child in self.grid_lay.children:
                target_height += child.height

            self.grid_lay.height = target_height

    def update_connections(self):
        for conn in self.connections[:]:
            if not (conn.input.parent.valid and conn.output.parent.valid):
                self.connections.remove(conn)

    def remove_snippet(self, snippet):
        snippet.invalidate()
        self.snippet_area.remove_widget(snippet)
        self.update_connections()

    def generate(self):
        generate(self.get_state())
Ejemplo n.º 7
0
class LinePlot(Widget):

    viewport = ListProperty(None)
    line_width = NumericProperty(5.)
    line_color = ListProperty([0, .8, 1])
    border_width = NumericProperty(5)
    border_color = ListProperty([.3, .3, .3])
    flattened_points = ListProperty(None)
    tick_distance_x = NumericProperty(None)
    tick_distance_y = NumericProperty(None)
    tick_color = ListProperty([.3, .3, .3])
    select_circle = ListProperty([0, 0, 0])

    def __init__(self, points, **kwargs):
        # calculate some basic information about the data
        self.points = points
        self.points_x = zip(*points)[0]
        self.points_y = zip(*points)[1]

        # if we could figure out how to draw an arbitrary number of ticks in kv, this would be cleaner
        self.ticks = InstructionGroup()
        self.tick_translate = Translate()

        super(LinePlot, self).__init__(**kwargs)
        self.canvas.insert(0, self.ticks)
        self.viewport = (min(self.points_x), min(self.points_y),
                         max(self.points_x), max(self.points_y))

    # recalculate viewport when size changes
    def on_size(self, instance, value):
        self.on_viewport(None, self.viewport)

    def on_pos(self, instance, value):
        self.tick_translate.xy = self.x, self.y

    def on_viewport(self, instance, value):
        if value is None or len(value) != 4: return
        print value
        self.vp_width_convert = float(self.width) / (value[2] - value[0])
        self.vp_height_convert = float(self.height) / (value[3] - value[1])

        # calculate the actual display points based on the viewport and self.size
        self.display_points = [
            self.to_display_point(*p) for p in self.points
            if value[0] <= p[0] <= value[2] and value[1] <= p[1] <= value[3]
        ]
        self.flattened_points = [
            item for sublist in self.display_points for item in sublist
        ]
        self.draw_ticks()

    # it would be real nice if we could figure out how to do this in kv
    def draw_ticks(self):
        self.ticks.clear()
        self.ticks.add(Color(*self.tick_color, mode='rgb'))
        self.ticks.add(PushMatrix())
        self.ticks.add(self.tick_translate)
        if self.tick_distance_x is not None:
            first_x_tick = self.tick_distance_x * (
                int(self.viewport[0] / self.tick_distance_x) + 1)
            for x in drange(first_x_tick, self.viewport[2],
                            self.tick_distance_x):
                start = self.to_display_point(x, self.viewport[1])
                stop = self.to_display_point(x, self.viewport[3])
                self.ticks.add(
                    Line(points=[start[0], start[1], stop[0], stop[1]]))

        if self.tick_distance_y is not None:
            first_y_tick = self.tick_distance_y * (
                int(self.viewport[1] / self.tick_distance_y) + 1)
            for y in drange(first_y_tick, self.viewport[3],
                            self.tick_distance_y):
                start = self.to_display_point(self.viewport[0], y)
                stop = self.to_display_point(self.viewport[2], y)
                self.ticks.add(
                    Line(points=[start[0], start[1], stop[0], stop[1]]))

        self.ticks.add(PopMatrix())

    def to_display_point(self, x, y):
        return (self.vp_width_convert * (x - self.viewport[0]),
                self.vp_height_convert * (y - self.viewport[1]))

    def select_point(self, x, y):
        # get point from self.displaypoints that is closest to x

        distances = [abs((x - self.x) - d[0]) for d in self.display_points]
        idx = min(xrange(len(distances)), key=distances.__getitem__)
        self.select_circle = [
            self.display_points[idx][0], self.display_points[idx][1], 20
        ]
        return self.points[idx]

    def to_xy(self, x, y):
        xt = (x - self.x) / self.vp_width_convert + self.viewport[0]
        yt = (y - self.y) / self.vp_height_convert + self.viewport[1]
        return (xt, yt)
Ejemplo n.º 8
0
class TextureStack(Widget):
    """Several textures superimposed on one another, and possibly offset
    by some amount.

    In 2D games where characters can wear different clothes or hold
    different equipment, their graphics are often composed of several
    graphics layered on one another. This widget simplifies the
    management of such compositions.

    """
    texs = ListProperty()
    """Texture objects"""
    offxs = ListProperty()
    """x-offsets. The texture at the same index will be moved to the right
    by the number of pixels in this list.

    """
    offys = ListProperty()
    """y-offsets. The texture at the same index will be moved upward by
    the number of pixels in this list.

    """
    group = ObjectProperty()
    """My ``InstructionGroup``, suitable for addition to whatever ``canvas``."""

    def _get_offsets(self):
        return zip(self.offxs, self.offys)

    def _set_offsets(self, offs):
        offxs = []
        offys = []
        for x, y in offs:
            offxs.append(x)
            offys.append(y)
        self.offxs, self.offys = offxs, offys

    offsets = AliasProperty(
        _get_offsets,
        _set_offsets,
        bind=('offxs', 'offys')
    )
    """List of (x, y) tuples by which to offset the corresponding texture."""
    _texture_rectangles = DictProperty({})
    """Private.

    Rectangle instructions for each of the textures, keyed by the
    texture.

    """

    def __init__(self, **kwargs):
        """Make triggers and bind."""
        kwargs['size_hint'] = (None, None)
        self.translate = Translate(0, 0)
        self.group = InstructionGroup()
        super().__init__(**kwargs)
        self.bind(offxs=self.on_pos, offys=self.on_pos)

    def on_texs(self, *args):
        """Make rectangles for each of the textures and add them to the canvas."""
        if not self.canvas or not self.texs:
            Clock.schedule_once(self.on_texs, 0)
            return
        texlen = len(self.texs)
        # Ensure each property is the same length as my texs, padding
        # with 0 as needed
        for prop in ('offxs', 'offys'):
            proplen = len(getattr(self, prop))
            if proplen > texlen:
                setattr(self, prop, getattr(self, prop)[:proplen-texlen])
            if texlen > proplen:
                propval = list(getattr(self, prop))
                propval += [0] * (texlen - proplen)
                setattr(self, prop, propval)
        self.group.clear()
        self._texture_rectangles = {}
        w = h = 0
        (x, y) = self.pos
        self.translate.x = x
        self.translate.y = y
        self.group.add(PushMatrix())
        self.group.add(self.translate)
        for tex, offx, offy in zip(self.texs, self.offxs, self.offys):
            rect = Rectangle(
                pos=(offx, offy),
                size=tex.size,
                texture=tex
            )
            self._texture_rectangles[tex] = rect
            self.group.add(rect)
            tw = tex.width + offx
            th = tex.height + offy
            if tw > w:
                w = tw
            if th > h:
                h = th
        self.size = (w, h)
        self.group.add(PopMatrix())
        self.canvas.add(self.group)

    def on_pos(self, *args):
        """Translate all the rectangles within this widget to reflect the widget's position.

        """
        (x, y) = self.pos
        self.translate.x = x
        self.translate.y = y

    def clear(self):
        """Clear my rectangles and ``texs``."""
        self.group.clear()
        self._texture_rectangles = {}
        self.texs = []
        self.size = [1, 1]

    def insert(self, i, tex):
        """Insert the texture into my ``texs``, waiting for the creation of
        the canvas if necessary.

        """
        if not self.canvas:
            Clock.schedule_once(
                lambda dt: self.insert(i, tex), 0)
            return
        self.texs.insert(i, tex)

    def append(self, tex):
        """``self.insert(len(self.texs), tex)``"""
        self.insert(len(self.texs), tex)

    def __delitem__(self, i):
        """Remove a texture and its rectangle"""
        tex = self.texs[i]
        try:
            rect = self._texture_rectangles[tex]
            self.canvas.remove(rect)
            del self._texture_rectangles[tex]
        except KeyError:
            pass
        del self.texs[i]

    def __setitem__(self, i, v):
        """First delete at ``i``, then insert there"""
        if len(self.texs) > 0:
            self._no_upd_texs = True
            self.__delitem__(i)
            self._no_upd_texs = False
        self.insert(i, v)

    def pop(self, i=-1):
        """Delete the texture at ``i``, and return it."""
        return self.texs.pop(i)
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
class ScatterBase(ScatterLayout):
    name = StringProperty()
    _hold_triggered = BooleanProperty(False)
    selected = BooleanProperty(False)
    scale_min = NumericProperty(.2)
    do_rotation = BooleanProperty(False)
    do_scale = BooleanProperty(False)
    do_translation = BooleanProperty(False)
    parent_width = NumericProperty()
    parent_height = NumericProperty()

    # Check if the touch collides with any of the children widgets
    def collide_point(self, x, y):
        local_x,local_y = self.to_local(x, y)
        for child in self.content.walk(restrict=True):
            if child==self.content:
                continue
            if child.collide_point(local_x, local_y):
                return True
        return False

    def get_full_bbox_parent(self, *args):
        all_corners = []
        # Can also iterate through self.content.walk(restrict=True), but takes longer
        for child in self.content.children:
            if child == self.content:
                continue
            all_corners += [child.pos, (child.x, child.top), (child.right, child.y), (child.right, child.top)]
        all_corners_parent = [self.to_parent(*point) for point in all_corners]
        xmin = min([point[0] for point in all_corners_parent])
        ymin = min([point[1] for point in all_corners_parent])
        xmax = max([point[0] for point in all_corners_parent])
        ymax = max([point[1] for point in all_corners_parent])
        return (xmin, ymin), (xmax-xmin, ymax-ymin)
    full_bbox_parent = AliasProperty(get_full_bbox_parent, None)

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            self._hold_triggered = False

            # If this is the "selected widget", treat as a movable scatter
            if self.selected:
                return super(ScatterBase, self).on_touch_down(touch)

            # Only detect on_hold if there is no currently selected widget
            if not self.selected:
                Clock.schedule_once(self.on_hold, .6)

    def on_touch_up(self,touch):

        # Unschedule on_hold for short presses
        if not self._hold_triggered:
            Clock.unschedule(self.on_hold)

        if self.selected:
            super(ScatterBase, self).on_touch_up(touch)

    def on_hold(self, *args):
        app = App.get_running_app()

        # 1. If already selected, do nothing
        if self.selected:
            return

        # 2. If a selected widget already exists, do nothing
        if not app.root.manager.layout_screen.selected_widget:
            self._hold_triggered = True
            app.root.manager.layout_screen.edit_widget(self)

    def on_transform_with_touch(self,*args):
        self.check_widget()

    # When widget is changed, check to make sure it is still in bounds of mirror
    def check_widget(self, *args):
        (bbox_x,bbox_y),(bbox_width,bbox_height) = self.full_bbox_parent

        # 1. Size check
        if bbox_width > self.parent_width:
            widget_to_bbox_ratio = bbox_width/self.parent_width
            self.scale = self.scale/widget_to_bbox_ratio
        if bbox_height > self.parent_height:
            widget_to_bbox_ratio = bbox_height/self.parent_height
            self.scale = self.scale/widget_to_bbox_ratio

        # 2. Translation check - Make sure widget is within mirror
        bbox_right = bbox_x+bbox_width
        bbox_top = bbox_y+bbox_height
        if bbox_x < 0:
            self.x -= bbox_x
        if bbox_right > self.parent_width:
            self.x += self.parent_width - bbox_right
        if bbox_y < 0:
            self.y -= bbox_y
        if bbox_top > self.parent_height:
            self.y += self.parent_height - bbox_top

    def select(self, *args):

        # 0. Set as selected
        self.selected = True

        # 1. Locate outermost bounding box of widget
        all_corners = []
        for widget in self.content.walk(restrict=True):
            if widget == self.content:
                continue
            all_corners += [widget.pos, (widget.x, widget.top), (widget.right, widget.y), (widget.right, widget.top)]
        xmin = min([point[0] for point in all_corners])
        ymin = min([point[1] for point in all_corners])
        xmax = max([point[0] for point in all_corners])
        ymax = max([point[1] for point in all_corners])

        # 2. Draw outline around widget
        self.outline = InstructionGroup()
        self.outline.clear()
        self.outline.add(Color(.67, .816, .95, 1))
        self.outline.add(Line(points=[xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax],
                              width=2,
                              joint='none',
                              close=True))
        self.canvas.add(self.outline)

        # 3. Bring to front, make movable
        self.do_translation = True
        self.do_scale = True
        if self.rotation != 0:
            self.do_rotation = True
        self.auto_bring_to_front = True

    def unselect(self, *args):

        # 0. Deselect
        self.selected = False

        # 1. Remove outline
        self.canvas.remove(self.outline)

        # 2. Lock widget
        self.do_translation = False
        self.do_scale = False
        self.do_rotation = False
        self.auto_bring_to_front = False
Ejemplo n.º 11
0
class PrinterAnimation(RelativeLayout):
    padding = NumericProperty(40)

    printer_actual_dimensions = ListProperty([10, 10, 80])
    printer_current_actual_height = NumericProperty(0.0)

    print_area_height = NumericProperty(1)
    print_area_width = NumericProperty(1)
    print_area_left = NumericProperty(0)
    print_area_bottom = NumericProperty(40)

    container_padding = NumericProperty(0)
    container_left = NumericProperty(0)
    container_width = NumericProperty(0)
    container_bottom = NumericProperty(0)
    container_height = NumericProperty(0)

    laser_size = ListProperty([40, 40])

    resin_height = NumericProperty(20)
    water_height = NumericProperty(20)

    scale = NumericProperty(1.0)

    resin_color = ListProperty([0.0, 0.8, 0.0, 0.6])
    water_color = ListProperty([0.2, 0.2, 1.0, 0.6])
    container_color = ListProperty([1.0, 1.0, 1.0, 1.0])
    laser_color_edge2 = ListProperty([0.0, 0.0, 0.5, 1.0])
    laser_color_edge = ListProperty([0.0, 0.0, 1.0, 1.0])
    laser_color = ListProperty([0.7, 1.0, 1.0, 1.0])

    drip_history = ListProperty()
    laser_points = ListProperty()

    middle_x = NumericProperty(52)

    laser_pos = NumericProperty(60)
    laser_speed = NumericProperty(1)
    refresh_rate = NumericProperty(1.0)

    def __init__(self, **kwargs):
        super(PrinterAnimation, self).__init__(**kwargs)
        self.drip_time_range = 5
        self.waiting_for_drips = True
        self.refresh_rate = App.get_running_app().refresh_rate
        self._gl_setup()
        self.axis_history = []
        self.drips = 0

        self.line_x = []
        self.line_y = []
        self.last_height = 0.0
        self.min_height = 0.0
        self.last_x_min = 0.0
        self.last_x_max = 1.0
        self.is_on_canvas = False

    def on_printer_actual_dimensions(self, instance, value):
        self.min_height = self.printer_actual_dimensions[2] / 400.0
        self.on_size(None)

    def _gl_setup(self):
        self.drip_texture = CoreImage("resources/images/drop.png", mipmap=True).texture
        self.drips_instruction = InstructionGroup()
        self.model_instruction = InstructionGroup()
        print(self.canvas.children)
        # self.canvas.add(self.drips_instruction)
        # self.canvas.add(self.model_instruction)

    def on_size(self, *largs):
        bounds_y = (self.height * 0.7) - self.resin_height
        bounds_x = self.width - (self.padding * 2)
        printer_x = self.printer_actual_dimensions[0]
        printer_y = self.printer_actual_dimensions[2]
        self.laser_pos = self.width / 2

        self.scale = min(bounds_y / printer_y, bounds_x / printer_x)
        Logger.info("Scale: {}".format(self.scale))
        self.print_area_width = printer_x * self.scale
        self.print_area_height = printer_y * self.scale

    def redraw(self, key):
        self._draw_drips()
        self._draw_laser()
        self._draw_model()
        if not self.is_on_canvas:
            self.canvas.insert(4, self.drips_instruction)
            self.canvas.insert(4, self.model_instruction)
            self.is_on_canvas = True

        Clock.unschedule(self.redraw)
        Clock.schedule_once(self.redraw, self.refresh_rate)

    def animation_start(self, *args):
        Clock.unschedule(self.redraw)
        self.axis_history = []
        self.line_x = []
        self.line_y = []
        self.last_height = 0
        self.min_height = 0.0
        self.laser_points = []
        self.drip_history = []
        Clock.schedule_once(self.redraw, self.refresh_rate)

    def animation_stop(self):
        Clock.unschedule(self.redraw)
        self.axis_history = []
        self.line_x = []
        self.line_y = []
        self.last_height = 0
        self.min_height = 0.0
        self.laser_points = []
        self.drip_history = []

    def _draw_drips(self):
        self.drips_instruction.clear()
        self.drips_instruction.add(Color(1, 1, 1, 1))
        top = time.time()
        bottom = top - self.drip_time_range
        drips_in_history = len(self.drip_history)
        for (index, drip_time) in zip(range(drips_in_history, 0, -1), self.drip_history):
            if drip_time > bottom:
                time_ago = top - drip_time
                y_pos_percent = (self.drip_time_range - time_ago) / self.drip_time_range
                drip_pos_y = (self.height * y_pos_percent) + self.padding
                xoff = 10 + math.sin((len(self.drip_history) - index) / (2 * math.pi)) * 20
                self.drips_instruction.add(Rectangle(size=[12, 16], pos=[self.print_area_left + xoff, drip_pos_y], texture=self.drip_texture))

    def _draw_laser(self):
        if self.waiting_for_drips:
            self.laser_points = []
        else:
            x_min = self.print_area_left + (self.last_x_min * self.print_area_width)
            x_max = self.print_area_left + (self.last_x_max * self.print_area_width)
            if (self.laser_pos >= x_max):
                self.laser_pos = x_max
                self.laser_speed = abs(self.laser_speed) * -1
            if (self.laser_pos <= x_min):
                self.laser_pos = x_min
                self.laser_speed = abs(self.laser_speed)

            self.laser_pos += self.laser_speed
            laser_x = self.laser_pos
            self.laser_points = [self.middle_x, self.height - self.padding,
                                 laser_x,          self.water_height + self.print_area_bottom + self.resin_height]

    def _draw_model(self):
            if self.axis_history:
                model_height = self.axis_history[-1][2]
                if model_height > (self.last_height + self.min_height) or not self.line_x:
                    x1, y1, x2, y2 = self._get_pixels(self.axis_history[-1])
                    self.last_x_min = x1
                    self.last_x_max = x2

                    self.line_x.insert(0, x1)
                    self.line_x.append(x2)
                    self.line_y.insert(0, y1)
                    self.line_y.append(y2)
                    self.last_height = model_height

                    points = []
                    for idx in range(0, len(self.line_x)):
                        x = int(self.print_area_left + (self.line_x[idx] * self.print_area_width))
                        y = int(self.print_area_bottom + self.resin_height + (self.line_y[idx] * self.print_area_height)) - 2
                        points.append(x)
                        points.append(y)
                    self.model_instruction.clear()
                    self.model_instruction.add(Color(rgba=(1.0, 0.0, 0.0, 1.0)))
                    self.model_instruction.add(Line(points=points, width=2, close=True))

    def _get_pixels(self, data):
        pixel_height = data[2] / self.printer_actual_dimensions[2]
        pixel_pos_min = (data[0][0] + (self.printer_actual_dimensions[1] / 2.0)) / self.printer_actual_dimensions[1]
        pixel_pos_max = (data[0][1] + (self.printer_actual_dimensions[1] / 2.0)) / self.printer_actual_dimensions[1]
        return [pixel_pos_min, pixel_height, pixel_pos_max, pixel_height]
Ejemplo n.º 12
0
class MapViewer(ScatterPlane):
    def __init__(self, **kwargs):
        kwargs.setdefault('do_rotation', False)
        kwargs.setdefault('show_border', False)
        kwargs.setdefault('close_on_idle', False)
        kwargs.setdefault('scale_min', 1)
        super(MapViewer, self).__init__(**kwargs)  # init ScatterPlane with above parameters

        self.map = None

        self.tilesize = (0, 0)
        self.tileCache = TileCache()

        self._zoom = 0  # intern var managed by a property
        self.reticule = None
        self.show_arrow = False
        self.arrow = type('DefaultArrow', (), {'azimuth': 0})  # creation a a default object, so azimth can still be set

        self.idle = True  # used by self.update

        # variable contenant la dernière position gps
        self.last_pos = (0, 0)
        self.last_pos_wgs84 = (0, 0)

        self.locked_on_pos = False

        # The path
        self.path = None
        self.path_width = 5.0
        self.tracking_path = False
        self._path_zoom = 0  # intern var to track a zoom change

        # Layers
        self.map_layer = None
        self.path_layer = None

        # Variables relative to layers
        self.map_cleanup_scheduled = False

        # Reposition tasks
        self.reposition_executor = None

        # Finally, as every user action implies a change of x and/or y value,
        # we bind those properties change on the update
        self.bind(x=self.update, y=self.update)

    def view_map(self, _map):
        # Prepare the map
        self._prepare_map(_map)

        # Set the last pos in map coord if possible
        self._init_last_pos(_map)

        # Adding the map layer
        self.map_layer = InstructionGroup()
        self.canvas.add(self.map_layer)

        # Adding the path layer
        self.path_layer = InstructionGroup()
        self.path_layer.add(Color(1, 0, 0, 0.5))
        self.path = Line(width=self.path_width)
        self.path_layer.add(self.path)
        self.canvas.add(self.path_layer)

        # Creation of the reposition task executor
        # Update the reticule and if needed the arrow position
        self.reposition_executor = RepositionExecutor(0.1)
        if self.reticule is not None:
            self.reposition_executor.add_reposition_task(lambda: self.set_reticule_pos(*self.last_pos))
        self.reposition_executor.add_reposition_task(lambda: self.set_arrow_pos())

        # The first time we view a map we have to trigger the first tile drawing and reposition the reticule
        Clock.schedule_once(self.update_map, 0)
        self.reposition_executor.execute()

    def view_map_for_calibration(self, _map):
        # Prepare the map
        self._prepare_map(_map)

        # Adding the map layer
        self.map_layer = InstructionGroup()
        self.canvas.add(self.map_layer)

        # Creation of the reposition task executor
        self.reposition_executor = RepositionExecutor(0.1)

        # The first time we view a map we have to trigger the first tile drawing and reposition the reticule
        Clock.schedule_once(self.update_map, 0)

    def _prepare_map(self, _map):
        # Cleanup the canvas, reinitialize variables
        self.canvas.clear()
        self.idle = True
        self.scale = 1
        self.pos = 0, 0
        self.tileCache.__init__()

        self.map = _map
        self.tilesize = _map.get_tile_size()

        # Here we set the maximum scale of the Scatter, information retrieved from max_zoom in calibration.ini
        self.scale_max = pow(2, _map.get_max_zoom())

        # Save the path of the map's calibration file in application settings
        config.save_last_map_path(_map.calibration_file_path)

    def update(self, obj, value, delay=UPDATE_DELAY):
        """
        To prevent overuse of update_map, each change of position triggers an update of the map after UPDATE_DELAY sec.
        For instance, the position of the reticule has to be updated.
        """
        if self.map is None:
            return

        # Pause the Loader, to prevent ui blocking
        Loader.pause()

        # Reposition widgets that need to be repositioned
        self.reposition_executor.execute()

        # Trigger the map update
        if self.idle:
            self.idle = False
        else:
            Clock.unschedule(self.update_map)
        Clock.schedule_once(self.update_map, delay)

        # Resume the Loader after a short time
        Clock.schedule_once(MapViewer.resume_loading, 0.1)

    def update_map(self, dt):
        # First, we get the current area covered on the ScatterPlane
        area = self.get_covered_area()
        map_area = (0, 0, self.tilesize[0], self.tilesize[1])

        # Then, we make the list of tiles corresponding to that area
        tile_list = self.generate_tile_list(self.zoom, map_area, area)

        # Before drawing the tiles, we may have to perform a cleanup of the map layer
        if self.map_cleanup_scheduled:
            self.map_layer.clear()
            self.tileCache.__init__()
            self.map_cleanup_scheduled = False

        # Tiles drawing
        self.draw_tiles(tile_list)

        # We schedule a cleanup, which will be done if the app is inactive at the time of the callback execution
        Clock.schedule_once(partial(self.cleanup, tile_list), CLEANUP_DELAY)

        self.idle = True
        # logger.debug("container : %s" % self.tileCache.container.values())

        # If we are showing the path, we update its view (adjust the thickness)
        if self.tracking_path:
            self.format_path()

    @staticmethod
    def resume_loading(dt):
        Loader.resume()

    @property
    def zoom(self):
        """Get zoom from current scale"""
        # At each zoom step forward, we cover an area twice as small as the former area
        self._zoom = log(self.scale, 2)
        return self._zoom

    def get_covered_area(self):
        parent = self.parent
        xmin = parent.x
        xmax = parent.x + parent.width
        ymin = parent.y
        ymax = parent.y + parent.height

        # Coordinates in ScatterPlane
        # Here, local and window coordinates are the same because ScatterPlane's origin
        # corresponds to the windows's origin
        xmin, ymin = self.to_local(xmin, ymin)  # (x,y) coord of the bottom left corner
        xmax, ymax = self.to_local(xmax, ymax)  # (x,y) coord of the top right corner

        return xmin, ymin, xmax, ymax

    def out_of_scope(self):
        logger.debug("OUT OF SCOPE")
        return []

    @staticmethod
    def extend_tile_view(tab, maxindex):
        if len(tab):
            if tab[0] > 0:
                tab.insert(0, tab[0] - 1)
            if tab[-1] < maxindex - 1:
                tab.append(tab[-1] + 1)

    def generate_tile_list(self, zoom, map_area, area):
        """ Generates the list of tiles that should be visible for the given zoom level
         and the area visible on the scatterPlane """
        xmin0, ymin0, xmax0, ymax0 = map_area  # area for zoom=0, one unique tile
        xmin, ymin, xmax, ymax = area

        # Overlap test
        if xmax <= xmin0 or xmin >= xmax0 or ymin >= ymax0 or ymax <= ymin0:
            return self.out_of_scope()

        # coordinates of the intersection between map_area and area
        xmin_inter = max(xmin0, xmin)
        xmax_inter = min(xmax0, xmax)
        ymin_inter = max(ymin0, ymin)
        ymax_inter = min(ymax0, ymax)

        # If the current zoom is already an integer, we take its value.
        # Otherwise, we take its superior int value because we want to
        # identify which tiles of the next zoom level have to be displayed
        zoom_int = int(zoom if zoom == int(zoom) else int(zoom) + 1)
        targeted_scale = pow(2, zoom_int)
        tile_width = (xmax0 - xmin0) / float(targeted_scale)

        startx_index = int(xmin_inter / tile_width)
        endx_index = int(xmax_inter / tile_width)

        # Calculation of the indexes on x axis
        x_indexes = []
        append = x_indexes.append
        for x in range(startx_index, endx_index + 1):
            append(x)
        MapViewer.extend_tile_view(x_indexes, targeted_scale)

        starty_index = int(ymin_inter / tile_width)
        endy_index = int(ymax_inter / tile_width)

        # Calculation of the indexes on y axis
        y_indexes = []
        append = y_indexes.append
        for y in range(starty_index, endy_index + 1):
            append(y)
        MapViewer.extend_tile_view(y_indexes, targeted_scale)

        tile_list = []
        append = tile_list.append
        for x in x_indexes:
            for y in y_indexes:
                tile = Tile()
                # tile.canvas = self.canvas
                tile.pos = Vector(x, y) * tile_width
                tile.size = (tile_width, tile_width)
                tile.x = x
                tile.y = y
                tile.zoom = zoom_int
                append(tile)

        return tile_list

    # The good damn right way to do it
    def draw_tile(self, proxy):
        if proxy.image.texture:
            self.map_layer.add(
                Rectangle(pos=proxy.pos, size=proxy.size, texture=proxy.image.texture, group=proxy.zoom))

    def draw_tiles(self, tile_list):
        for tile in tile_list:
            image_id = tile.get_id()
            if self.tileCache.add_tile(tile.zoom, image_id):
                image = self.map.get_tile(tile.zoom, tile.x, tile.y)
                if image is None:
                    continue
                image.create_property("pos", tile.pos)
                image.create_property("size", tile.size)
                image.create_property("zoom", str(int(tile.zoom)))
                image.bind(on_load=self.draw_tile)
                # if image.loaded:    # only useful when Loader actually caches images
                #     image.dispatch("on_load")

    def cleanup(self, tile_list, *largs):
        """
        Cleanup is achieved when the app is considered inactive, ie when self.idle = True.
        """
        if not self.idle:
            return
        zoom = self.zoom
        zoom_int = int(zoom if zoom == int(zoom) else int(zoom) + 1)

        print "debut cleanup, conserve zoom", zoom_int
        for _zoom in TileCache.get_unnecessary_zooms(self.tileCache.container.keys(), zoom_int):
            try:
                print "suppr zoom", _zoom
                self.map_layer.remove_group(str(_zoom))
                self.tileCache.remove_tiles_for_zoom(_zoom)
            except:
                logger.debug("the canvas doesn't contains the zoom %s" % _zoom)

        if self.tileCache.is_tile_overfull(zoom_int):
            self.map_cleanup_scheduled = True

        # logger.debug("cleanup done, container : %s" % self.tileCache.container.values())

    def set_reticule(self, reticule):
        self.reticule = reticule

    def set_reticule_pos(self, x, y):
        """
        :param x: x position on the map in the range [0,tile_width]
        :param y: y position on the map in the range [0,tile_height]
        :return:
        """
        # TODO : remplacer cette méthode par set_movable_widget_pos ? (cf plus bas)
        self.reticule.set_center_x(self.x + x * self.scale)
        self.reticule.set_center_y(self.y + y * self.scale)

    def set_movable_widget_pos(self, widget, x, y):
        """
        Position a widget given the local coordinates of its center.
        :param x: x position on the map in the range [0,tile_width]
        :param y: y position on the map in the range [0,tile_height]
        """
        if widget:
            widget.set_center_x(self.x + x * self.scale)
            widget.set_center_y(self.y + y * self.scale)

    def add_movable_widget(self, widget):
        """Actions done when a movable widget is added to the MapViewer."""

        # Add a reposition task
        self.reposition_executor.add_reposition_task(lambda: self.set_movable_widget_pos(
            widget, *widget.pos_local))

    def set_arrow_pos(self):
        """Must be called after set_reticule_pos"""
        if self.show_arrow:
            self.arrow.set_center_x(self.reticule.get_center_x())
            self.arrow.set_center_y(self.reticule.get_center_y())

    def set_orientation_arrow(self, arrow):
        self.show_arrow = True
        self.arrow = arrow

    def update_azimuth(self, instance, angle):
        self.arrow.azimuth = -angle

    def _init_last_pos(self, _map):
        """Initialize the last position in the _map projection coordinates, given the last known wgs84 position.
        This is used in view_map method.
        """
        if self.last_pos_wgs84 is not None:
            self.last_pos = _map.get_map_coord(*self.last_pos_wgs84)
        else:
            self.last_pos = (0, 0)

    def update_pos(self, instance, value):
        # Remember the position, even if there is no map
        self.last_pos_wgs84 = value

        # If there is no map, no need to go further
        if self.map is None:
            return

        # Conversion from wgs84 coord to map coord
        x, y = self.map.get_map_coord(*value)

        # Remember this position too
        self.last_pos = x, y

        # Update the reticule pos
        self.set_reticule_pos(x, y)

        # Update the orientation arrow pos if necessary
        if self.show_arrow:
            self.set_arrow_pos()

        # Update the path
        if self.tracking_path:
            self.update_path(x, y)

        # If we are locked on pos, we center the view on the last known position
        if self.locked_on_pos:
            self.center_on_last_pos()

    def update_path(self, x, y):
        self.path.points += x, y

    def toggle_tracking_path(self, obj):
        if self.tracking_path:
            self.tracking_path = False
            # Creation of a new path
            self.path = Line(width=self.path_width)
        else:
            self.tracking_path = True

    def format_path(self, *args):
        """This updates the width of the path to be consistent with the scale.
        """
        if self._path_zoom != self.zoom:
            self.path.width = self.path_width / self.scale
            self._path_zoom = self.zoom

    def center_on_last_pos(self, *args):
        scale = self.scale
        new_x = Window.size[0]/2 - self.last_pos[0]*scale
        new_y = Window.size[1]/2 - self.last_pos[1]*scale
        Animation.cancel_all(self)
        anim = Animation(x=new_x, y=new_y, t='in_out_quad', duration=0.5)
        anim.start(self)

    def get_dist_to_center(self):
        """
        :return: The distance in meters between the last known position and the position represented by the middle
        of the screen. If no distance can be calculated, it returns -1.
        """
        if self.map is None:
            return -1
        try:
            merc_x, merc_y = self.map.map_coord_to_map_projection(*self.to_local(Window.size[0]/2, Window.size[1]/2))
        except ZeroDivisionError:
            return -1
        lat, lon = Map.to_geographic(merc_x, merc_y)
        lat_last, lon_last = self.last_pos_wgs84
        dist = Map.distance(lat_last, lon_last, lat, lon)
        return dist

    def transform_with_touch(self, touch):
        if self.locked_on_pos:
            if len(self._touches) == 1:
                return False

            changed = False
            # We have more than one touch... list of last known pos
            points = [Vector(self._last_touch_pos[t]) for t in self._touches
                      if t is not touch]
            # Add current touch last
            points.append(Vector(touch.pos))

            # We only want to transform if the touch is part of the two touches
            # farthest apart! So first we find anchor, the point to transform
            # around as another touch farthest away from current touch's pos
            anchor_ = max(points[:-1], key=lambda p: p.distance(touch.pos))

            # Now we find the touch farthest away from anchor, if its not the
            # same as touch. Touch is not one of the two touches used to transform
            farthest = max(points, key=anchor_.distance)
            if farthest is not points[-1]:
                return changed

            # Ok, so we have touch, and anchor, so we can actually compute the
            # transformation
            old_line = Vector(*touch.ppos) - anchor_
            new_line = Vector(*touch.pos) - anchor_
            if not old_line.length():   # div by zero
                return changed

            # pol : we don't want rotation here
            # angle = radians(new_line.angle(old_line)) * self.do_rotation
            # self.apply_transform(Matrix().rotate(angle, 0, 0, 1), anchor=anchor)

            # pol : trick -> change the origin!!
            anchor = Vector(self.to_parent(*self.last_pos))
            if self.do_scale:
                scale = new_line.length() / old_line.length()
                new_scale = scale * self.scale
                if new_scale < self.scale_min:
                    scale = self.scale_min / self.scale
                elif new_scale > self.scale_max:
                    scale = self.scale_max / self.scale
                self.apply_transform(Matrix().scale(scale, scale, scale),
                                     anchor=anchor)
                changed = True
            return changed

        super(MapViewer, self).transform_with_touch(touch)
Ejemplo n.º 13
0
class PrinterAnimation(RelativeLayout):
    padding = NumericProperty(40)

    printer_actual_dimensions = ListProperty([10, 10, 80])
    printer_current_actual_height = NumericProperty(0.0)

    print_area_height = NumericProperty(1)
    print_area_width = NumericProperty(1)
    print_area_left = NumericProperty(0)
    print_area_bottom = NumericProperty(40)

    container_padding = NumericProperty(0)
    container_left = NumericProperty(0)
    container_width = NumericProperty(0)
    container_bottom = NumericProperty(0)
    container_height = NumericProperty(0)

    laser_size = ListProperty([40, 40])

    resin_height = NumericProperty(20)
    water_height = NumericProperty(20)

    scale = NumericProperty(1.0)

    resin_color = ListProperty([0.0, 0.8, 0.0, 0.6])
    water_color = ListProperty([0.2, 0.2, 1.0, 0.6])
    container_color = ListProperty([1.0, 1.0, 1.0, 1.0])
    laser_color_edge2 = ListProperty([0.0, 0.0, 0.5, 1.0])
    laser_color_edge = ListProperty([0.0, 0.0, 1.0, 1.0])
    laser_color = ListProperty([0.7, 1.0, 1.0, 1.0])

    drip_history = ListProperty()
    laser_points = ListProperty()

    middle_x = NumericProperty(52)

    laser_pos = NumericProperty(60)
    laser_speed = NumericProperty(1)
    refresh_rate = NumericProperty(1.0)

    def __init__(self, **kwargs):
        super(PrinterAnimation, self).__init__(**kwargs)
        self.drip_time_range = 5
        self.waiting_for_drips = True
        self.refresh_rate = App.get_running_app().refresh_rate
        self._gl_setup()
        self.axis_history = []
        self.drips = 0

        self.line_x = []
        self.line_y = []
        self.last_height = 0.0
        self.min_height = 0.0
        self.last_x_min = 0.0
        self.last_x_max = 1.0
        self.is_on_canvas = False

    def on_printer_actual_dimensions(self, instance, value):
        self.min_height = self.printer_actual_dimensions[2] / 400.0
        self.on_size(None)

    def _gl_setup(self):
        self.drip_texture = CoreImage("resources/images/drop.png", mipmap=True).texture
        self.drips_instruction = InstructionGroup()
        self.model_instruction = InstructionGroup()
        print(self.canvas.children)
        # self.canvas.add(self.drips_instruction)
        # self.canvas.add(self.model_instruction)

    def on_size(self, *largs):
        bounds_y = (self.height * 0.7) - self.resin_height
        bounds_x = self.width - (self.padding * 2)
        printer_x = self.printer_actual_dimensions[0]
        printer_y = self.printer_actual_dimensions[2]
        self.laser_pos = self.width / 2

        self.scale = min(bounds_y / printer_y, bounds_x / printer_x)
        Logger.info("Scale: {}".format(self.scale))
        self.print_area_width = printer_x * self.scale
        self.print_area_height = printer_y * self.scale

    def redraw(self, key):
        self._draw_drips()
        self._draw_laser()
        self._draw_model()
        if not self.is_on_canvas:
            self.canvas.insert(4, self.drips_instruction)
            self.canvas.insert(4, self.model_instruction)
            self.is_on_canvas = True

        Clock.unschedule(self.redraw)
        Clock.schedule_once(self.redraw, self.refresh_rate)

    def reset(self):
        self.axis_history = []
        self.line_x = []
        self.line_y = []
        self.last_height = 0
        self.min_height = 0.0
        self.laser_points = []
        self.drip_history = []
        self.waiting_for_drips = True
        self.printer_current_actual_height = 0
        self.redraw('')
        Clock.unschedule(self.redraw)

    def animation_start(self, *args):
        Clock.unschedule(self.redraw)
        self.axis_history = []
        self.line_x = []
        self.line_y = []
        self.last_height = 0
        self.min_height = 0.0
        self.laser_points = []
        self.drip_history = []
        Clock.schedule_once(self.redraw, self.refresh_rate)

    def animation_stop(self):
        Clock.unschedule(self.redraw)
        self.axis_history = []
        self.line_x = []
        self.line_y = []
        self.last_height = 0
        self.min_height = 0.0
        self.laser_points = []
        self.drip_history = []

    def _draw_drips(self):
        self.drips_instruction.clear()
        self.drips_instruction.add(Color(1, 1, 1, 1))
        top = time.time()
        bottom = top - self.drip_time_range
        drips_in_history = len(self.drip_history)
        for (index, drip_time) in zip(range(drips_in_history, 0, -1), self.drip_history):
            if drip_time > bottom:
                time_ago = top - drip_time
                y_pos_percent = (self.drip_time_range - time_ago) / self.drip_time_range
                drip_pos_y = (self.height * y_pos_percent) + self.padding
                xoff = 10 + math.sin((len(self.drip_history) - index) / (2 * math.pi)) * 20
                self.drips_instruction.add(Rectangle(size=[12, 16], pos=[self.print_area_left + xoff, drip_pos_y], texture=self.drip_texture))

    def _draw_laser(self):
        if self.waiting_for_drips:
            self.laser_points = []
        else:
            x_min = self.print_area_left + (self.last_x_min * self.print_area_width)
            x_max = self.print_area_left + (self.last_x_max * self.print_area_width)
            if (self.laser_pos >= x_max):
                self.laser_pos = x_max
                self.laser_speed = abs(self.laser_speed) * -1
            if (self.laser_pos <= x_min):
                self.laser_pos = x_min
                self.laser_speed = abs(self.laser_speed)

            self.laser_pos += self.laser_speed
            laser_x = self.laser_pos
            self.laser_points = [self.middle_x, self.height - self.padding,
                                 laser_x,          self.water_height + self.print_area_bottom + self.resin_height]

    def _draw_model(self):
            if self.axis_history:
                model_height = self.axis_history[-1][2]
                if model_height > (self.last_height + self.min_height) or not self.line_x:
                    x1, y1, x2, y2 = self._get_pixels(self.axis_history[-1])
                    self.last_x_min = x1
                    self.last_x_max = x2

                    self.line_x.insert(0, x1)
                    self.line_x.append(x2)
                    self.line_y.insert(0, y1)
                    self.line_y.append(y2)
                    self.last_height = model_height

                    points = []
                    for idx in range(0, len(self.line_x)):
                        x = int(self.print_area_left + (self.line_x[idx] * self.print_area_width))
                        y = int(self.print_area_bottom + self.resin_height + (self.line_y[idx] * self.print_area_height)) - 2
                        points.append(x)
                        points.append(y)
                    self.model_instruction.clear()
                    self.model_instruction.add(Color(rgba=(1.0, 0.0, 0.0, 1.0)))
                    self.model_instruction.add(Line(points=points, width=2, close=True))
            else:
                self.model_instruction.clear()

    def _get_pixels(self, data):
        pixel_height = data[2] / self.printer_actual_dimensions[2]
        pixel_pos_min = (data[0][0] + (self.printer_actual_dimensions[1] / 2.0)) / self.printer_actual_dimensions[1]
        pixel_pos_max = (data[0][1] + (self.printer_actual_dimensions[1] / 2.0)) / self.printer_actual_dimensions[1]
        return [pixel_pos_min, pixel_height, pixel_pos_max, pixel_height]
Ejemplo n.º 14
0
class TextureStack(Widget):
    """Several textures superimposed on one another, and possibly offset
    by some amount.

    In 2D games where characters can wear different clothes or hold
    different equipment, their graphics are often composed of several
    graphics layered on one another. This widget simplifies the
    management of such compositions.

    """
    texs = ListProperty()
    """Texture objects"""
    offxs = ListProperty()
    """x-offsets. The texture at the same index will be moved to the right
    by the number of pixels in this list.

    """
    offys = ListProperty()
    """y-offsets. The texture at the same index will be moved upward by
    the number of pixels in this list.

    """
    def _get_offsets(self):
        return zip(self.offxs, self.offys)

    def _set_offsets(self, offs):
        offxs = []
        offys = []
        for x, y in offs:
            offxs.append(x)
            offys.append(y)
        self.offxs, self.offys = offxs, offys

    offsets = AliasProperty(_get_offsets,
                            _set_offsets,
                            bind=('offxs', 'offys'))
    """List of (x, y) tuples by which to offset the corresponding texture."""
    _texture_rectangles = DictProperty({})
    """Private.

    Rectangle instructions for each of the textures, keyed by the
    texture.

    """

    def __init__(self, **kwargs):
        """Make triggers and bind."""
        kwargs['size_hint'] = (None, None)
        self.translate = Translate(0, 0)
        self.group = InstructionGroup()
        super().__init__(**kwargs)
        self.bind(offxs=self.on_pos, offys=self.on_pos)

    def on_texs(self, *args):
        """Make rectangles for each of the textures and add them to the
        canvas, taking their stacking heights into account.

        """
        if not self.canvas or not self.texs:
            Clock.schedule_once(self.on_texs, 0)
            return
        texlen = len(self.texs)
        # Ensure each property is the same length as my texs, padding
        # with 0 as needed
        for prop in ('offxs', 'offys'):
            proplen = len(getattr(self, prop))
            if proplen > texlen:
                setattr(self, prop, getattr(self, prop)[:proplen - texlen])
            if texlen > proplen:
                propval = list(getattr(self, prop))
                propval += [0] * (texlen - proplen)
                setattr(self, prop, propval)
        self.group.clear()
        self._texture_rectangles = {}
        w = h = 0
        (x, y) = self.pos
        self.translate.x = x
        self.translate.y = y
        self.group.add(PushMatrix())
        self.group.add(self.translate)
        for tex, offx, offy in zip(self.texs, self.offxs, self.offys):
            rect = Rectangle(pos=(offx, offy), size=tex.size, texture=tex)
            self._texture_rectangles[tex] = rect
            self.group.add(rect)
            tw = tex.width + offx
            th = tex.height + offy
            if tw > w:
                w = tw
            if th > h:
                h = th
        self.size = (w, h)
        self.group.add(PopMatrix())
        if self.group not in self.canvas.children:
            self.canvas.add(self.group)

    def on_pos(self, *args):
        """Translate all the rectangles within this widget to reflect the widget's position.

        """
        (x, y) = self.pos
        Logger.debug("TextureStack: repositioning to {}".format((x, y)))
        self.translate.x = x
        self.translate.y = y

    def clear(self):
        """Clear my rectangles, ``texs``, and ``stackhs``."""
        self.group.clear()
        self._texture_rectangles = {}
        self.texs = []
        self.offxs = []
        self.offys = []
        self.size = [1, 1]

    def insert(self, i, tex):
        """Insert the texture into my ``texs``, waiting for the creation of
        the canvas if necessary.

        """
        if not self.canvas:
            Clock.schedule_once(lambda dt: TextureStack.insert(self, i, tex),
                                0)
            return
        self.texs.insert(i, tex)
        self.offxs.insert(i, 0)
        self.offys.insert(i, 0)

    def append(self, tex):
        """``self.insert(len(self.texs), tex)``"""
        self.insert(len(self.texs), tex)

    def __delitem__(self, i):
        """Remove a texture, its rectangle, and its stacking height"""
        tex = self.texs[i]
        try:
            rect = self._texture_rectangles[tex]
            self.canvas.remove(rect)
            del self._texture_rectangles[tex]
        except KeyError:
            pass
        del self.offxs[i]
        del self.offys[i]
        del self.texs[i]

    def __setitem__(self, i, v):
        """First delete at ``i``, then insert there"""
        if len(self.texs) > 0:
            self._no_upd_texs = True
            self.__delitem__(i)
            self._no_upd_texs = False
        self.insert(i, v)

    def pop(self, i=-1):
        """Delete the offsets and texture at ``i``, returning the texture.

        """
        del self.offxs[i]
        del self.offys[i]
        return self.texs.pop(i)
Ejemplo n.º 15
0
class String(FloatLayout):
    open_note_val = NumericProperty(0)
    num_frets = NumericProperty(12)
    fret_positions = ListProperty()
    note_vals = ListProperty()
    mode_filter = NumericProperty(0b101011010101)
    root_note_idx = NumericProperty(0)
    scale_text = StringProperty("")
    notes_to_highlight = StringProperty("")
    notes_or_octaves = StringProperty("")

    animation_prop = NumericProperty(0)
    hit_prop = NumericProperty(0)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.string_shadow = Rectangle()
        self.string_graphic = Rectangle()
        self.note_markers = InstructionGroup()
        self.octave_markers = InstructionGroup()

        self.canvas.add(Color(rgba=[0 / 255, 0 / 255, 0 / 255, 0.25]))
        self.canvas.add(self.string_shadow)
        self.canvas.add(Color(rgba=[169 / 255, 169 / 255, 169 / 255, 1]))
        self.canvas.add(self.string_graphic)
        self._add_markers()
        self.canvas.add(self.note_markers)
        self.canvas.add(self.octave_markers)
        self.bind(size=self.update_canvas, pos=self.update_canvas)

        self.anim = Animation()
        self.hit_anim = Animation()
        self.play_instrs = []

    def _add_markers(self):
        for i in range(25):
            marker = Marker()
            self.note_markers.add(marker)

    def animate_marker(self, index, *args):
        markers = self.note_markers.children
        anim = Animation(animation_prop=1, duration=0.5, t="in_circ")
        anim.bind(on_start=markers[index].initiate_animation)
        anim.bind(on_progress=markers[index].update_animation)
        anim.bind(on_complete=markers[index].end_animation)
        anim.start(self)

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            for i in range(len(self.note_markers.children)):
                self.animate_marker(i)

    def update_canvas(self, *args):
        if self.fret_positions:  # self.fret_positions is empty during instantiation.
            # self.update_octave_markers()
            self.update_string_graphics()
            self.update_note_markers()

    def update_string_graphics(self):
        w, h = self.width, self.height * 0.1
        x, y = self.pos
        cy = y + (self.height / 2)
        string_y = cy - (h / 2)
        shadow_height = 3 * h
        shadow_y = string_y - shadow_height
        # Shadow effect.
        self.string_shadow.size = [w, shadow_height]
        self.string_shadow.pos = [x, cy - shadow_height]
        # String.
        self.string_graphic.size = [w, h]
        self.string_graphic.pos = [x, string_y]

    def update_note_markers(self, *args):
        x, y = self.pos
        r1 = self.height / 2
        r2 = r1 * 0.9
        rdiff = r1 - r2
        for i, (note_val, marker) in enumerate(
                zip(self.note_vals, self.note_markers.children)):
            # Make right edge of circle touch left edge of fret bar (where your finger should go!)
            fret_left = self.fret_positions[i] - (
                self.fretboard.fret_bar_width / 2)
            # Draw 2 concentric circles, c1 and c2.
            # Circles are defined by a square's lower left corner.
            c1x, c1y = (fret_left - 2 * r1) + x, y
            c2x, c2y = c1x + rdiff, c1y + rdiff

            octave, note_idx = divmod(note_val, 12)
            included = int(
                bin(self.mode_filter)[2:][note_idx - self.root_note_idx])
            highlighted = int(
                bin(scale_highlights[self.notes_to_highlight])[2:][
                    note_idx - self.root_note_idx])
            if self.notes_or_octaves == "Notes":
                color_idx = note_idx - self.root_note_idx
                color = rainbow[color_idx]
            else:
                color_idx = octave - 1
                color = octave_colors[color_idx]

            if self.scale_text == "Scale Degrees":
                note_idx -= self.root_note_idx

            note_text = scale_texts[self.scale_text][note_idx]

            marker.update(i, note_text, c1x, c1y, r1, c2x, c2y, r2, included,
                          highlighted, color)

    def update_octave_markers(self):
        self.octave_markers.clear()
        for i, note_val in enumerate(self.note_vals):
            self.update_octave_marker(i, note_val)

    def update_octave_marker(self, i, note_val):
        if self.fret_ranges:
            octave = (note_val - self.fretboard.root_note_idx) // 12
            left, right = self.fret_ranges[i]
            width = right - left
            self.octave_markers.add(octave_colors[octave])
            self.octave_markers.add(
                Rectangle(pos=[left, 0], size=[width, self.height]))

    def on_open_note_val(self, instance, value):
        self.note_vals = [
            val for val in range(self.open_note_val, self.open_note_val + 25)
        ]
        self.update_canvas(instance, value)

    def on_num_frets(self, instance, value):
        self.note_vals = [
            val for val in range(self.open_note_val, self.open_note_val + 25)
        ]
        self.update_canvas(instance, value)

    def on_root_note_idx(self, instance, value):
        self.update_canvas(instance, value)

    def on_mode_filter(self, instance, value):
        self.update_canvas(instance, value)

    def on_scale_text(self, instance, value):
        self.update_note_markers()

    def on_notes_to_highlight(self, instance, value):
        self.update_note_markers()

    def on_notes_or_octaves(self, *args):
        self.update_note_markers()

    ### SONG PLAYING METHODS
    def play_thread(self, lead_in):
        # The GuitarPro songs' tempo are of form BPM where the B(eat) is always a quarter note.
        thread = Thread(target=partial(self._play_thread_animation, lead_in),
                        daemon=True)
        thread.start()

    def _play_thread_animation(self, lead_in):
        self.stopped = False
        markers = self.note_markers.children
        idx = 0
        time.sleep(lead_in)
        start = time.time()
        goal = start
        while not self.stopped:
            if idx == len(self.play_instrs):
                return
            fret_num, seconds = self.play_instrs[idx]
            if fret_num != -1:
                # self._play_fret(fret_num, seconds)

                self.animation_prop = 0
                anim = Animation(animation_prop=1, duration=seconds)
                anim.bind(on_start=markers[fret_num].initiate_animation)
                anim.bind(on_progress=markers[fret_num].update_animation)
                anim.bind(on_complete=markers[fret_num].end_animation)
                self.anim = anim

                self.hit_prop = 0
                hit_anim = Animation(hit_prop=1, duration=min(seconds, 0.1))
                hit_anim.bind(
                    on_start=markers[fret_num].initiate_hit_animation)
                hit_anim.bind(
                    on_progress=markers[fret_num].update_hit_animation)
                hit_anim.bind(on_complete=markers[fret_num].end_hit_animation)
                self.hit_anim = hit_anim

                anim.start(self)
                hit_anim.start(self)
            goal += seconds
            idx += 1
            time.sleep(max(goal - time.time(), 0))

    # def _play_fret(self, fret_num, seconds):
    #     self.anim.stop(self)
    #     self.animation_prop = 0
    #     markers = self.note_markers.children
    #     anim = Animation(animation_prop=1, duration=seconds)
    #     anim.bind(on_start=markers[fret_num].initiate_animation)
    #     anim.bind(on_progress=markers[fret_num].update_animation)
    #     anim.bind(on_complete=markers[fret_num].end_animation)
    #     self.anim = anim
    #     anim.start(self)

    def stop(self):
        self.stopped = True