예제 #1
0
class Timeline(AppElementSingle):
    def __init__(self):
        self.current_frame = 1
        self.start_frame = 1
        self.end_frame = 2
        self.time_last = -1
        self.frame_rate = AppSettings.standard_fps
        self.playing = False

    # ------------------------------------------------------------------------ #
    # App element default

    def setup_ui(self):
        x = 30
        frame_range = (self.start_frame, self.end_frame)

        self.fps = Widgets.int_roller(self.p, self.frame_rate, self.update_frame_rate, AppSettings.fps_range, xy=(x, 2))
        x += 52

        Widgets.button(self.p, "<-", self.play_backward, (x, 0))
        x += 90
        Widgets.button(self.p, "||", self.play_stop, (x, 0))
        x += 90
        Widgets.button(self.p, "->", self.play_forward, (x, 0))
        x += 100

        self.frame_roller = Widgets.int_roller(
            self.p, self.current_frame, self.set_current_frame, frame_range, xy=(x, 2)
        )
        x += 50

        self.ticker = Widgets.ticker(self.p, self.tick)
        self.update_frame_rate()

    def keyPressEvent(self, e):
        if self.p.query_key("-", e.key()):
            self.tweak(-1)

        if self.p.query_key("=", e.key()):
            self.tweak(+1)

    def init_panel(self):
        self.p = Panel(AppSettings.timeline_size)
        self.p.draw = self.draw
        self.p.setup_ui = self.setup_ui
        self.p.init()

        # turns off default IO
        self.p.mouseReleaseEvent = self.empty
        self.p.mouseMoveEvent = self.empty
        self.p.wheelEvent = self.empty
        self.p.keyPressEvent = self.keyPressEvent
        return self.p

    def draw(self, d):
        sd = -AppSettings.timeline_size[0] * 0.4
        ed = +AppSettings.timeline_size[0] * 0.4
        hh = AppSettings.timeline_size[1] * 0.1
        sf = self.start_frame
        ef = self.end_frame

        def lerp_drawing_range(frame):
            t = (frame - sf) / float(ef - sf)
            return sd + t * (ed - sd)

        # Timeline graph
        d.fill((0, 0, 0), a=0)
        d.stroke(Colors.null, a=125)
        d.line((sd - 10, 0), (ed + 10, 0))
        for frame in range(sf, ef + 1):
            x = lerp_drawing_range(frame)
            d.line((x, -hh), (x, +hh))

        # Current time marker
        d.fill(Colors.time_marker, a=200)
        d.stroke((0, 0, 0), a=0)
        x = lerp_drawing_range(self.current_frame)
        d.box((x, 0), (10, hh * 2))

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Widget

    def tick(self, *args):
        self.current_frame += 1 * self.playing
        self.bound_current_frame()
        self.frame_roller.setValue(self.current_frame)
        Scene().evaluate_frame(self.current_frame)
        Animation.evaluate_frame(self.current_frame)
        self.p.update()

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Menu

    def play_forward(self):
        self.playing = 1

    def play_stop(self):
        self.playing = 0

    def play_backward(self):
        self.playing = -1

    def update_frame_rate(self):
        self.frame_rate = self.fps.value()
        self.ticker.start(1000.0 / self.frame_rate)

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Playback interface

    def set_frame_range(self, s, f):
        self.start_frame = s
        self.end_frame = f
        self.n_frames = f - s + 1
        self.frame_roller.setMinimum(s)
        self.frame_roller.setMaximum(f)

    def set_current_frame(self, frame):
        self.current_frame = frame
        self.bound_current_frame()
        Scene().evaluate_frame(self.current_frame)
        self.p.update()

    def bound_current_frame(self):
        if self.current_frame > self.end_frame:
            self.current_frame = self.start_frame

        if self.current_frame < self.start_frame:
            self.current_frame = self.end_frame

    def tweak(self, change):
        self.current_frame += change
        self.bound_current_frame()
        Scene().evaluate_frame(self.current_frame)
        self.p.update()
예제 #2
0
class Animation(AppElementSpawnable):

    # ------------------------------------------------------------------------ #
    # Functions that will update all instances (useful for update ticks)

    @staticmethod
    def evaluate_frame(frame):
        for inst in Animation._instances:
            inst.p.update()

    @staticmethod
    def mocap_reset():
        for inst in Animation._instances:
            inst.update_motion_menu()

    def update_motion_menu(self):
        m = [(name, self.set_motion_curve_fn(name), False, False) for name in MocapHandle().get_names()]
        self.motion_menu.setMenu(Widgets.menu(self.p, m))

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # --

    def __init__(self, ref_toon, ref_name):

        # Motion
        self.ref_toon = ref_toon
        self.ref_name = ref_name
        self.mc_handle = ""

        # Algorithms
        self.params = Graph.params

        self.preprocess_curve = []
        self.keyframes = []
        self.splines = []

        # Metrics
        self.metric_map = MetricMap()

        # Display, and other
        self.image_save_index = 0
        self.display = Graph.draw_type[Graph.draw_type_default]

    def setup_ui(self):
        x = 10
        inc = 100

        self.joint_display_name = Widgets.label(self.p, self.ref_name, (x, 4))
        self.mc_handle_label = Widgets.label(self.p, self.mc_handle, (x, 24))
        x += 50

        # -------------------------------------------------------------------- #
        # Menus

        m = [("None", self.empty, False, False)]
        self.motion_menu = Widgets.button_menu(self.p, "Motion", m, (x, 0))
        self.update_motion_menu()
        x += inc

        # Application of curve
        m = [
            ("Clear", self.clear_motion, False, False),
            ("tx", self.apply_curve_fn("tx"), False, False),
            ("ty", self.apply_curve_fn("ty"), False, False),
            ("rz", self.apply_curve_fn("rz"), False, False),
        ]
        Widgets.button_menu(self.p, "Apply", m, (x, 0))
        x += inc

        # Grepping curvers from existing toon
        m = [
            ("tx", self.grep_curve_fn("tx"), False, False),
            ("ty", self.grep_curve_fn("ty"), False, False),
            ("rz", self.grep_curve_fn("rz"), False, False),
        ]
        Widgets.button_menu(self.p, "Grep", m, (x, 0))
        x += inc

        # Display menu
        m = [(key, self.toggle_display_fn(key), True, self.display[key]) for key in self.display.keys()]
        Widgets.button_menu(self.p, "Display", m, (x, 0))
        x += inc

        # Algorithms menu
        m = [
            ("Subdivide", self.subdivide, False, False),
            ("Douglas Peucker", self.douglas_peucker, False, False),
            ("Salient Points 1D", self.salient_points_1d, False, False),
            ("Salient Points FT", self.salient_points_ft, False, False),
            ("Salient Points it", self.salient_points_iter, False, False),
            ("Polyline", self.polyline, False, False),
            ("Flat tangents", self.flat_tangents, False, False),
            ("One-pass", self.cubic, False, False),
            ("Reset", self.reset_simplification, False, False),
            ("Save Graph", self.save_graph, False, False),
        ]
        Widgets.button_menu(self.p, "Simplify", m, (x, 0))
        x += inc

        #
        # -------------------------------------------------------------------- #

        # -------------------------------------------------------------------- #
        # Sliders

        x = 100
        y = AppSettings.anim_size[3] - 150
        inc = 20

        self.sliders = {}
        for key in self.params.keys():
            if key != "":
                slider = Widgets.slider(
                    self.p, key, self.update_slider_fn(key), mm=(1, 20), xy=(x, y), v=self.params[key]
                )
                self.sliders[key] = slider
            y += inc

        #
        # -------------------------------------------------------------------- #

        # -------------------------------------------------------------------- #
        # Metric map

        x = AppSettings.anim_size[2] - 100
        y = AppSettings.anim_size[3] * 0.5
        m = self.metric_map.m
        self.metric_map_handle = {}

        for key in m.keys():
            inner_m = m[key]
            l = Widgets.label(self.p, key, xy=(x - 50, y))
            y += 20
            self.metric_map_handle[key] = {}

            for inner_key in inner_m:
                l = Widgets.label(self.p, inner_key, xy=(x, y))
                y += 20
                self.metric_map_handle[key][inner_key] = l

        #
        # -------------------------------------------------------------------- #

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Key map

    def keyPressEvent(self, e):

        # Switch focus to current selection
        if self.p.query_key("e", e.key()):
            index, joint = Scene().get_selected()
            self.ref_toon = index
            self.ref_name = joint.name
            self.joint_display_name.setText(self.ref_name)

        # Fast save graph
        if self.p.query_key("s", e.key()):
            self.image_save_index += 1
            index = self.image_save_index
            self.save_graph("%s/%s" % (AppSettings.default_graph_save_dir, "fast_save_" + str(index)) + ".svg")

        # Reset fast index
        if self.p.query_key("q", e.key()):
            self.image_save_index = 0

        # Auto displays
        display_presets = ["1", "2", "3", "4", "5", "6"]
        for key in display_presets:
            if self.p.query_key(key, e.key()):
                self.toggle_display_type(key)

        # Algorithm hotkeys
        if self.p.query_key("y", e.key()):
            self.subdivide()
        if self.p.query_key("u", e.key()):
            self.douglas_peucker()
        if self.p.query_key("i", e.key()):
            self.salient_points_1d()
        if self.p.query_key("o", e.key()):
            self.salient_points_ft()
        if self.p.query_key("p", e.key()):
            self.salient_points_iter()
        if self.p.query_key("j", e.key()):
            self.cubic()
        if self.p.query_key("k", e.key()):
            self.flat_tangents()
        if self.p.query_key("l", e.key()):
            self.polyline()

        # Camera controls
        if self.p.query_key("x", e.key()):
            self.p.toggle_camera_lock()
        if self.p.query_key("c", e.key()):
            self.p.store_camera()
        if self.p.query_key("v", e.key()):
            self.p.recover_camera()

        # Quit hotkey
        if self.p.query_key("esc", e.key()):
            self.p.window_obj.close()

        self.p.update()

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Panel stuff

    def init_panel(self):
        self.p = Panel((AppSettings.anim_size[2], AppSettings.anim_size[3]))
        self.p.draw = self.draw
        self.p.setup_ui = self.setup_ui
        self.p.keyPressEvent = self.keyPressEvent
        self.p.init()
        return self.p

    def draw(self, d):
        sd = -AppSettings.anim_size[2] * 0.4
        ed = +AppSettings.anim_size[2] * 0.4
        sh = -AppSettings.anim_size[3] * 0.4
        eh = +AppSettings.anim_size[3] * 0.4
        s = Timeline().start_frame
        f = Timeline().end_frame

        def lerp_over_drawing(p_i):
            x, y = p_i
            t = (x - s) / float(f - s)
            return sd + t * (ed - sd)

        def y_scaling(p_i):
            x, y = p_i
            return y * self.params["y"]

        def curve_as_points(color, curve, offset=0.0, sizable=False):
            s = 1.0
            if sizable:
                s = self.params["mocap"]

            d.fill(color, a=125)
            d.stroke((0, 0, 0), a=0)
            for i in range(len(curve)):
                x = lerp_over_drawing(curve[i])
                y = y_scaling(curve[i]) + offset
                d.circle((x, y), s)

        def curve_as_polyline(color, curve):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=125)
            for i in range(len(curve)):
                x1 = lerp_over_drawing(curve[i - 1])
                x2 = lerp_over_drawing(curve[i])
                y1 = y_scaling(curve[i - 2])
                y2 = y_scaling(curve[i - 1])
                d.line((x1, y1), (x2, y2))

        def curve_as_steps(color, curve):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=125)
            for i in range(len(curve)):
                x1 = lerp_over_drawing(curve[i - 1])
                x2 = lerp_over_drawing(curve[i])
                y1 = y_scaling(curve[i - 2])
                y2 = y_scaling(curve[i - 1])
                d.line((x1, y1), (x2, y1))
                d.line((x2, y1), (x2, y2))

        def curve_as_tangents(color, curve, tvs):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=125)
            for i in range(len(curve)):
                tv = curve[i] + tvs[i]

                x1 = lerp_over_drawing(curve[i])
                y1 = y_scaling(curve[i])
                x2 = lerp_over_drawing(tv)
                y2 = y_scaling(tv)

                dv = np.array([x2 - x1, y2 - y1])
                dv = dv / np.linalg.norm(dv) * self.params["vector_size"]
                x2 = x1 + dv[0]
                y2 = y1 + dv[1]
                d.line((x1, y1), (x2, y2))

        def curve_as_normals(color, curve, tvs):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=125)
            for i in range(len(curve)):
                tv = curve[i] + tvs[i]

                x1 = lerp_over_drawing(curve[i])
                y1 = y_scaling(curve[i])
                x2 = lerp_over_drawing(tv)
                y2 = y_scaling(tv)

                dv = np.array([x2 - x1, y2 - y1])
                dv = dv / np.linalg.norm(dv) * self.params["vector_size"]
                x2 = x1 - dv[1]
                y2 = y1 + dv[0]
                d.line((x1, y1), (x2, y2))

        def keyframes_as_dots(color, curve, keyframes):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=255)

            d.fill(color, a=255)
            d.stroke((0, 0, 0), a=0)
            for k in keyframes:
                x = lerp_over_drawing(curve[k])
                y = y_scaling(curve[k])
                d.circle((x, y), 3)

        def spline_as_curves(color, interps):
            d.fill(color, a=255)
            d.stroke((0, 0, 0), a=0)
            for interp in interps:
                x = lerp_over_drawing(interp)
                y = y_scaling(interp)
                d.circle((x, y), 2)

        def spline_as_cubic(color, controls):
            d.fill((0, 0, 0), a=0)
            d.stroke(color, a=255)
            controls_scaled = [[lerp_over_drawing(c), y_scaling(c)] for c in controls]
            d.bezier(controls_scaled)

        def spline_as_handles(color, controls):
            controls_scaled = [[lerp_over_drawing(c), y_scaling(c)] for c in controls]
            p0, p1, p2, p3 = controls_scaled
            d.fill(color, a=255)
            d.stroke(color, a=255)
            d.line(p0, p1)
            d.line(p2, p3)
            d.stroke((0, 0, 0), a=0)
            d.box(p1, (5, 5))
            d.box(p2, (5, 5))

        if self.display["axis"]:
            d.fill((0, 0, 0), a=0)
            d.stroke(Colors.null, a=125)
            x = AppSettings.anim_size[2] * 0.5
            d.line((-x, 0), (+x, 0))
            x = lerp_over_drawing(s)
            d.line((x, sh), (x, eh))
            x = lerp_over_drawing(f)
            d.line((x, sh), (x, eh))

        if self.display["time"]:
            d.fill((0, 0, 0), a=0)
            d.stroke(Colors.time_marker, a=125)
            x = lerp_over_drawing(Timeline().current_frame)
            d.line((x, sh), (x, eh))

        # Motion curves
        if self.mc_handle != "":
            curve = MocapHandle().get_curve(self.mc_handle)

            if self.display["mocap_dots"]:
                curve_as_points(Colors.mocap, curve, sizable=True)

            if self.display["mocap_lines"]:
                curve_as_polyline(Colors.mocap, curve)

            if self.display["mocap_steps"]:
                curve_as_steps(Colors.mocap, curve)

            if self.display["preprocess"] and self.preprocess_curve != []:
                curve_as_points(Colors.preprocess, self.preprocess_curve)

            if self.display["tangents"] and self.preprocess_curve != []:
                tvs = TangentApprox.from_motion_curve(self.preprocess_curve)
                curve_as_tangents(Colors.tangents, self.preprocess_curve, tvs)

            if self.display["normals"] and self.preprocess_curve != []:
                tvs = TangentApprox.from_motion_curve(self.preprocess_curve)
                curve_as_normals(Colors.normals, self.preprocess_curve, tvs)

            if self.display["keyframes"]:
                keyframes_as_dots(Colors.keyframes, self.preprocess_curve, self.keyframes)

            if self.display["splines"]:
                for spline in self.splines:
                    spline_as_cubic(Colors.splines, spline.list_controls())

            if self.display["handles"]:
                for spline in self.splines:
                    spline_as_handles(Colors.handles, spline.list_controls())

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Menu callbacks

    def save_meta_data(self, filepath):
        curve = MocapHandle().get_curve(self.mc_handle)
        keyframes = self.keyframes
        spline_handles = [s.list_controls()[1:3] for s in self.splines]

        meta_data = {
            "motion": {
                "file": MocapHandle().last_loaded,
                "name": self.ref_name,
                "curve_key": self.mc_handle,
                "curve": curve.tolist(),
            },
            "simplify": {"keyframes": keyframes, "splines": spline_handles},
            "metric": self.metric_map.m,
        }

        f = open(filepath, "w")
        f.write(json.dumps(meta_data, sort_keys=True, indent=4, separators=(",", ": ")))
        f.close()

    def save_graph(self, filepath=""):
        if filepath == "":
            filepath = Dialogs.save_file(self.p, AppSettings.demuddle_dir)[0]

        data_fp = filepath + ".meta.json"
        self.p.paintImage(filepath)
        self.save_meta_data(data_fp)

    def set_motion_curve_fn(self, name):
        def fn():
            self.mc_handle = name
            self.mc_handle_label.setText(self.mc_handle)

        return fn

    def clear_motion(self):
        # TODO implement this
        # Scene().command_delete_motion(self.ref_toon, self.ref_name)
        self.p.update()

    def apply_curve_fn(self, target):
        def fn():
            Scene().command_add_motion(self.ref_toon, self.ref_name, target, self.mc_handle)
            self.p.update()

        return fn

    def grep_curve_fn(self, target):
        def fn():
            mobj = Scene().command_get_motion(self.ref_toon, self.ref_name, target)
            if mobj != None:
                self.mc_handle = "%s, %s" % (mobj.ref_name, mobj.ref_dof)
                self.mc_handle_label.setText(self.mc_handle)
            self.p.update()

        return fn

    def update_slider_fn(self, key):
        def fn(v):
            self.params[key] = v

        return fn

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Simplification

    def subdivide(self):
        curve = MocapHandle().get_curve(self.mc_handle)

        preprocess = Subdivide()
        preprocess.update_curve(curve)
        preprocess.update(self.params)
        self.preprocess_iters = preprocess.get_iters()
        self.preprocess_curve = preprocess.get_result()

        Scene().set_status("Curve preproccesed (Subdivision)", "normal")
        self.p.update()

    def douglas_peucker(self):
        curve = self.preprocess_curve

        keyframer = DouglasPeucker()
        keyframer.update_curve(curve)
        keyframer.update(self.params)
        self.keyframes = keyframer.get_keyframes()

        Scene().set_status("Keyframe selected (Douglas Peucker)", "normal")
        self.p.update()

    def salient_points_1d(self):
        curve = self.preprocess_curve

        keyframer = SalientPoints1D()
        keyframer.update_curve(curve)
        keyframer.learn()
        self.keyframer = keyframer

        Scene().set_status("zerotable (Salient Points, 1D)", "normal")
        self.p.update()

    def salient_points_ft(self):
        curve = self.preprocess_curve

        keyframer = SalientPointsFlatTangents()
        keyframer.update_curve(curve)
        keyframer.learn()
        self.keyframer = keyframer

        Scene().set_status("zerotable (Salient Points, FT)", "normal")
        self.p.update()

    def salient_points_iter(self):
        self.keyframer.update(self.params)
        self.keyframes = self.keyframer.get_keyframes()
        Scene().set_status("Keyframe selected (Salient Points, iter)", "normal")
        self.p.update()

    def polyline(self):
        curve = self.preprocess_curve

        keyframes = self.keyframes
        segments = zip(keyframes[:-1], keyframes[1:])

        self.splines = []
        for (k1, k2) in segments:
            spline_obj = PolyLine()
            spline_obj.update_curve(curve[k1 : k2 + 1])
            spline_obj.update(self.params)
            spline_obj.run()
            self.splines.append(spline_obj)

        self.update_metric_map()
        Scene().set_status("Interpolated (polyline)", "normal")
        self.p.update()

    def flat_tangents(self):
        curve = self.preprocess_curve

        keyframes = self.keyframes
        segments = zip(keyframes[:-1], keyframes[1:])

        self.splines = []
        for (k1, k2) in segments:
            spline_obj = FlatTangent()
            spline_obj.update_curve(curve[k1 : k2 + 1])
            spline_obj.update(self.params)
            spline_obj.run()
            self.splines.append(spline_obj)

        self.update_metric_map()
        Scene().set_status("Interpolated (flat spline)", "normal")
        self.p.update()

    def cubic(self):
        curve = self.preprocess_curve

        keyframes = self.keyframes
        segments = zip(keyframes[:-1], keyframes[1:])

        self.splines = []
        for (k1, k2) in segments:
            spline_obj = OnePass()
            spline_obj.update_curve(curve[k1 : k2 + 1])
            spline_obj.update(self.params)
            spline_obj.run()
            self.splines.append(spline_obj)

        self.update_metric_map()
        Scene().set_status("Interpolated (cubic spline)", "normal")
        self.p.update()

    def reset_simplification(self):
        self.mc_handle = ""
        self.preprocess_curve = []
        self.keyframes = []
        self.splines = []
        Scene().set_status("keyframes deleted", "normal")
        self.p.update()

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Metrics

    def update_metric_map(self):

        # Make a curve from spline pieces
        approx = []
        approx_length = 0
        for spline in self.splines:
            approx += spline.interps_nearest()[:-1]
            approx_length += spline.get_length()
        approx += [self.splines[-1].interps_nearest()[-1]]

        # Measure errors
        a = [v for v in self.preprocess_curve]
        b = [v for v in approx]
        self.metric_map.m["distance"]["normOne"] = Distance.normOne(a, b)
        self.metric_map.m["distance"]["normTwo"] = Distance.normTwo(a, b)
        self.metric_map.m["distance"]["normInf"] = Distance.normInf(a, b)
        self.metric_map.m["distance"]["meanSqu"] = Distance.meanSqu(a, b)

        # Measure lengths
        self.metric_map.m["length"]["original"] = 0  # TODO implement data length
        self.metric_map.m["length"]["simplified"] = approx_length

        # Record numbers of critical points
        c_org = len(Critical.local_minima(a) + Critical.local_maxima(a))
        c_simp = len(Critical.local_minima(b) + Critical.local_maxima(b))
        self.metric_map.m["critical"]["original"] = c_org
        self.metric_map.m["critical"]["simplified"] = c_simp

        # Save data to map
        m = self.metric_map_handle
        for key in m.keys():
            inner_m = m[key]
            for inner_key in inner_m:
                v = self.metric_map.m[key][inner_key]
                inner_m[inner_key].setText("%s, %2.2f" % (inner_key, v))

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Display stuff

    def toggle_display_fn(self, item):
        def fn(*args):
            self.display[item] = self.display[item] == False
            self.p.update()

        return fn

    def toggle_display_type(self, index):
        self.display = Graph.draw_type[index]
        self.p.update()
예제 #3
0
class Scene(AppElementSingle):

    # ------------------------------------------------------------------------ #
    # --

    def __init__(self):
        self.toons = []
        self.toon_filepaths = []
        self.display = {"axis": True}

    def setup_ui(self):

        w = 0

        menu = [
            ("Load Scene", self.load_scene, False, False),
            ("Save Scene", self.save_scene, False, False),
            ("Load Mocap", self.load_mocap, False, False),
        ]
        Widgets.button_menu(self.p, "File", menu, (w, 0))
        w += 100

        menu = [
            ("Load", self.load_toon, False, False),
            ("Examine", self.examine_toon, False, False),
            ("Offset", self.rotate_toon, False, False),
            ("Scale", self.scale_toon, False, False),
        ]
        Widgets.button_menu(self.p, "Character", menu, (w, 0))
        w += 120

        menu = [("Axis", self.toggle_display_fn("axis"), True, True)]
        Widgets.button_menu(self.p, "Display", menu, (w, 0))
        w += 120

        self.status_bar = Widgets.label(
            self.p, " Status", (10, AppSettings.scene_size[1] - 20), w=AppSettings.scene_size[0] - 20
        )
        self.set_status("loaded %s" % self.toon_filepaths, "normal")

    def mousePressEvent(self, e):
        if self.p.query_button(2, e.buttons()):
            self.command_select(e.x(), e.y())

    def keyPressEvent(self, e):

        # Character hotkeys
        if self.p.query_key("left", e.key()):
            self.command_modify(Joint.Move_L)
        if self.p.query_key("right", e.key()):
            self.command_modify(Joint.Move_R)
        if self.p.query_key("down", e.key()):
            self.command_modify(Joint.Move_D)
        if self.p.query_key("up", e.key()):
            self.command_modify(Joint.Move_U)
        if self.p.query_key("a", e.key()):
            self.command_modify(Joint.Rot_L)
        if self.p.query_key("d", e.key()):
            self.command_modify(Joint.Rot_R)

        # Interface
        if self.p.query_key("e", e.key()):
            self.examine_toon()
        if self.p.query_key("esc", e.key()):
            Executable.quit()
        if self.p.query_key("-", e.key()):
            Timeline().tweak(-1)
        if self.p.query_key("=", e.key()):
            Timeline().tweak(+1)

        # Camera
        if self.p.query_key("x", e.key()):
            self.p.toggle_camera_lock()
        if self.p.query_key("c", e.key()):
            self.p.store_camera()
        if self.p.query_key("v", e.key()):
            self.p.recover_camera()

    def init_panel(self):
        self.p = Panel(AppSettings.scene_size)
        self.p.draw = self.draw
        self.p.setup_ui = self.setup_ui
        self.p.init()

        self.p.mousePressEvent = self.mousePressEvent
        self.p.keyPressEvent = self.keyPressEvent
        return self.p

    def draw(self, d):
        if self.display["axis"]:
            d.axis()
        for t in self.toons:
            t.draw(d)

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Toon API

    def add_toon_from_file(self, filepath):
        f = open(filepath, "r")
        toon = json.loads(f.read())
        f.close()

        t = Toon()
        for joint in toon["joints"]:
            joint_obj = t.joint_add(joint["name"], joint["parent"], joint["type"], joint["length"], joint["flip"])
            joint_obj.rotation_offset = joint["offset"]
            joint_obj.pos_scaling = joint["pos_s"]

        # Update cache and scene
        self.toon_filepaths.append(filepath)
        self.toons.append(t)
        self.p.update()

    def load_toon(self):
        filepath = Dialogs.open_file(self.p, AppSettings.toon_dir)[0]
        if os.path.isfile(filepath):
            self.add_toon_from_file(filepath)
        else:
            Scene().set_status("File selected is not valid", "error")

    def examine_toon(self):
        toon_index, selected = self.get_selected()
        if selected != None:
            Executable.spawn_window("Motion", AppSettings.anim_size, Animation(toon_index, selected.name).init_panel())

    def rotate_toon(self):
        for t in self.toons:
            if t.selection_active():
                v = Dialogs.int(self.p, "Rotate", "degrees anticlockwise")
                if v != None:
                    t.modify_rotation_offset(v)

    def scale_toon(self):
        for t in self.toons:
            if t.joint_selected() != None:
                v = Dialogs.float(self.p, "Scale", "scale character")
                if v != None:
                    t.modify_position_scaling(v)

    def get_selected(self):
        for i in range(len(self.toons)):
            t = self.toons[i]
            if t.joint_selected() != None:
                return i, t.joint_selected()
        return -1, None

    def curve_data_on_selected(self):
        for t in self.toons:
            if t.selection_active():
                return t.sele.get_motion_info()
        return None

    def command_select(self, x, y):
        already_found = False
        for t in self.toons:
            if t.selection_cast(x, y, already_found):
                already_found = True
        self.p.update()

    def command_modify(self, t_type):
        for t in self.toons:
            t.modify_interactive(t_type)
        self.p.update()

    def command_add_motion(self, character, name, target, curve_key):
        mobj = MocapHandle().generate_motion_obj(curve_key)

        joint_obj = self.toons[character].joint_by_name(name)
        joint_obj.add_retarget(target, mobj)
        self.p.update()

    def command_get_motion(self, character, name, target):
        joint_obj = self.toons[character].joint_by_name(name)
        if target in joint_obj.retargets:
            return joint_obj.retargets[target].motion
        else:
            Scene().set_status("Target not found in joint", "warning")
            return None

    def command_delete_motion(self, character, name):
        joint_obj = self.toons[character].joint_by_name(name)
        joint_obj.clear_retargets()
        self.p.update()

    def evaluate_frame(self, frame):
        for t in self.toons:
            t.evaluate_frame(frame)
        self.p.update()

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Loading and saving

    def load_mocap(self):
        MocapHandle().load_mocap()

    def save_scene(self):

        # cache character info and motions
        character_motion_maps = []
        character_infos = []
        for t in self.toons:
            motion_maps = {}
            character_info = {}
            for joint in t.joint_list():
                motion_maps[joint.name] = joint.generate_map_from_retargets()
                character_info[joint.name] = joint.get_info()

            character_motion_maps.append(motion_maps)
            character_infos.append(character_info)

        # organize into scene file
        scene_file = {
            "mocap": MocapHandle().last_loaded,
            "character_filepaths": self.toon_filepaths,
            "character_info": character_infos,
            "character_motion_maps": character_motion_maps,
        }

        # save
        filepath = Dialogs.save_file(self.p, AppSettings.save_dir)[0]
        f = open(filepath, "w")
        f.write(json.dumps(scene_file, sort_keys=True, indent=4, separators=(",", ": ")))
        f.close()

    def load_scene(self, filepath=""):
        if filepath == "":
            filepath = Dialogs.open_file(self.p, AppSettings.save_dir)[0]
        if not os.path.isfile(filepath):
            Scene().set_status("file selected is not valid.", "warning")
            return

        # open
        f = open(filepath, "r")
        scene_file = json.loads(f.read())
        f.close()

        # Load mocap (if it was there)
        if scene_file["mocap"] != "":
            MocapHandle().load_mocap("%s/%s" % (AppSettings.demuddle_dir, scene_file["mocap"]))

        # Load characters.
        for fp in scene_file["character_filepaths"]:
            self.add_toon_from_file("%s/%s" % (AppSettings.demuddle_dir, fp))

        # Apply info and motion onto characters
        cindex = 0
        for cindex in range(len(scene_file["character_motion_maps"])):
            motion_maps = scene_file["character_motion_maps"][cindex]
            character_infos = scene_file["character_info"][cindex]

            for joint_name in motion_maps.keys():
                character_info = character_infos[joint_name]
                motion_map = motion_maps[joint_name]
                joint_obj = self.toons[cindex].joint_by_name(joint_name)
                joint_obj.set_info(character_info)
                joint_obj.generate_retargets_from_map(motion_map)
            cindex += 1

        self.p.update()

    #
    # ------------------------------------------------------------------------ #

    # ------------------------------------------------------------------------ #
    # Other

    def toggle_display_fn(self, item):
        def fn(*args):
            self.display[item] = self.display[item] == False
            self.p.update()

        return fn

    def set_status(self, message, message_type):
        self.status_bar.setText(" Status: %s" % message)
        style = ""
        if message_type == "normal":
            style = "QLabel { background-color : #1573BD; color : white; }"
        elif message_type == "warning":
            style = "QLabel { background-color : #F6AC41; color : white; }"
        elif message_type == "error":
            stlye = "QLabel { background-color : #DE3B3C; color : white; }"
        else:
            pass
        self.status_bar.setStyleSheet(style)