Changeset - 0a47ec94fc63
[Not reviewed]
default
0 2 0
Drew Perttula - 12 years ago 2013-06-10 09:28:11
drewp@bigasterisk.com
move all point editing into Curve. fix manip to never try to make points have the same X. add check in Curve that points never have the same X or crossing X
Ignore-this: 75ad005fd9d35a61ecef82bf8ddae3f4
2 files changed with 63 insertions and 22 deletions:
0 comments (0 inline, 0 general)
light9/curvecalc/curve.py
Show inline comments
 
@@ -63,24 +63,40 @@ class Curve(object):
 
        p1,p2 = self.points[i],self.points[i+1]
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        return y
 
    __call__=eval
 

	
 
    def insert_pt(self, new_pt):
 
        """returns index of new point"""
 
        i = bisect(self.points, (new_pt[0],None))
 
        self.points.insert(i,new_pt)
 
        return i
 

	
 
    def set_points(self, updates):
 
        for i, pt in updates:
 
            self.points[i] = pt
 
            
 
        x = None
 
        for p in self.points:
 
            if p[0] <= x:
 
                raise ValueError("overlapping points")
 
            x = p[0]
 

	
 
    def pop_point(self, i):
 
        return self.points.pop(i)
 
            
 
    def remove_point(self, pt):
 
        self.points.remove(pt)
 
            
 
    def indices_between(self, x1, x2, beyond=0):
 
        leftidx = max(0, bisect(self.points, (x1,None)) - beyond)
 
        rightidx = min(len(self.points),
 
                       bisect(self.points, (x2,None)) + beyond)
 
        return range(leftidx, rightidx)
 
        
 
    def points_between(self, x1, x2):
 
        return [self.points[i] for i in self.indices_between(x1,x2)]
 

	
 
    def point_before(self, x):
 
        """(x,y) of the point left of x, or None"""
 
        leftidx = self.index_before(x)
light9/curvecalc/curveview.py
Show inline comments
 
@@ -47,25 +47,25 @@ class Sketch:
 
        dx = .01
 
        to_remove = []
 
        for i in range(1,len(pts)-1):
 
            x = pts[i][0]
 

	
 
            p_left = (x - dx, self.curveview.curve(x - dx))
 
            p_right = (x + dx, self.curveview.curve(x + dx))
 

	
 
            if angle_between(pts[i], p_left, p_right) > 160:
 
                to_remove.append(i)
 

	
 
        for i in to_remove:
 
            self.curveview.curve.points.remove(pts[i])
 
            self.curveview.curve.remove_point(pts[i])
 
            finalPoints.remove(pts[i])
 

	
 
        # the simplified curve may now be too far away from some of
 
        # the points, so we'll put them back. this has an unfortunate
 
        # bias toward reinserting the earlier points
 
        for i in to_remove:
 
            p = pts[i]
 
            if abs(self.curveview.curve(p[0]) - p[1]) > .1:
 
                self.curveview.add_point(p)
 
                finalPoints.append(p)
 
            
 
        self.curveview.update_curve()
 
@@ -144,59 +144,71 @@ class SelectManip(object):
 
            self.maxPointMove = min(moveLeft, moveRight)
 
        
 
        self.dragRange = (self.dragStartTime - moveLeft,
 
                          self.dragStartTime + moveRight)
 
        return True
 

	
 
    def onMotion(self, item, target_item, event, param):
 
        if hasattr(self, 'dragStartTime'):
 
            origPts = zip(self.getSelectedIndices(), self.origPoints)
 
            left = origPts[0][1][0]
 
            right = origPts[-1][1][0]
 
            width = right - left
 
            dontCross = .001
 

	
 
            clampLo = left if param == 'right' else self.dragRange[0]
 
            clampHi = right if param == 'left' else self.dragRange[1]
 

	
 
            def clamp(x, lo, hi):
 
                return max(lo, min(hi, x))
 
            
 
            mouseT = self.getWorldTime(event.x)
 
            clampedT = max(clampLo, min(clampHi, mouseT))
 
            clampedT = clamp(mouseT, clampLo + dontCross, clampHi - dontCross)
 

	
 
            dt = clampedT - self.dragStartTime
 

	
 
            if param == 'x':
 
                self.setPoints((i, (orig[0] + dt, orig[1]))
 
                               for i, orig in origPts)
 
            elif param == 'left':
 
                self.setPoints((i, (left + dt +
 
                                    (orig[0] - left) / width * (width - dt),
 
                self.setPoints((
 
                    i,
 
                    (left + dt +
 
                     (orig[0] - left) / width *
 
                     clamp(width - dt, dontCross, right - clampLo - dontCross),
 
                                    orig[1])) for i, orig in origPts)
 
            elif param == 'right':
 
                self.setPoints((i, (left +
 
                                    (orig[0] - left) / width * (width + dt),
 
                self.setPoints((
 
                    i,
 
                    (left +
 
                     (orig[0] - left) / width *
 
                     clamp(width + dt, dontCross, clampHi - left - dontCross),
 
                                    orig[1])) for i, orig in origPts)
 
            elif param == 'top':
 
                v = self.getWorldValue(event.y)
 
                if self.origMaxValue == 0:
 
                    self.setPoints((i, (orig[0], v)) for i, orig in origPts)
 
                else:
 
                    scl = max(0, min(1 / self.origMaxValue,
 
                                     v / self.origMaxValue))
 
                    self.setPoints((i, (orig[0], orig[1] * scl))
 
                                   for i, orig in origPts)
 

	
 
            elif param == 'centerScale':
 
                dt = mouseT - self.dragStartTime
 
                rad = width / 2
 
                tMid = left + rad
 
                maxScl = (rad + self.maxPointMove) / rad
 
                newWidth = max(0, min((rad + dt) / rad, maxScl)) * width
 
                maxScl = (rad + self.maxPointMove - dontCross) / rad
 
                newWidth = max(dontCross / width,
 
                               min((rad + dt) / rad, maxScl)) * width
 
                self.setPoints((i,
 
                                (tMid +
 
                                 ((orig[0] - left) / width - .5) * newWidth,
 
                                 orig[1])) for i, orig in origPts)
 
                
 

	
 
    def onRelease(self, item, target_item, event, param):
 
        if hasattr(self, 'dragStartTime'):
 
            del self.dragStartTime
 

	
 
    def update(self):
 
        """if the view or selection or selected point positions
 
@@ -456,25 +468,25 @@ class Curveview(object):
 
        getting redrawn many times. """
 
        self.redrawsEnabled = True
 
        self.update_curve()
 

	
 
    def knob_in(self, curve, value):
 
        """user turned a hardware knob, which edits the point to the
 
        left of the current time"""
 
        if curve != self.curve:
 
            return
 
        idx = self.curve.index_before(self.current_time())
 
        if idx is not None:
 
            pos = self.curve.points[idx]
 
            self.curve.points[idx] = (pos[0], value)
 
            self.curve.set_points([(idx, (pos[0], value))])
 
            self.update_curve()
 

	
 
    def slider_in(self, curve, value=None):
 
        """user pushed on a slider. make a new key.  if value is None,
 
        the value will be the same as the last."""
 
        if curve != self.curve:
 
            return
 

	
 
        if value is None:
 
            value = self.curve.eval(self.current_time())
 

	
 
        self.curve.insert_pt((self.current_time(), value))
 
@@ -507,45 +519,47 @@ class Curveview(object):
 
                getWorldValue=lambda y: self.world_from_screen(0, y)[1],
 
                getCanvasSize=lambda: self.size,
 
                setPoints=self.setPoints,
 
                getDragRange=self.getDragRange,
 
                )
 
        if not self.selected_points and self.selectManip:
 
            self.selectManip.destroy()
 
            self.selectManip = None
 

	
 
        self.selectionChanged()
 

	
 
    def getDragRange(self, idxs):
 
        """if you're dragging these points, what's the most time you
 
        can move left and right before colliding with another point"""
 
        """
 
        if you're dragging these points, what's the most time you can move
 
        left and right before colliding (exactly) with another
 
        point
 
        """
 
        maxLeft = maxRight = 99999
 
        cp = self.curve.points
 
        for i in idxs:
 
            nextStatic = i
 
            while nextStatic >= 0 and nextStatic in idxs:
 
                nextStatic -= 1
 
            if nextStatic >= 0:
 
                maxLeft = min(maxLeft, cp[i][0] - cp[nextStatic][0])
 

	
 
            nextStatic = i
 
            while nextStatic <= len(cp) - 1 and nextStatic in idxs:
 
                nextStatic += 1
 
            if nextStatic <= len(cp) - 1:
 
                maxRight = min(maxRight, cp[nextStatic][0] - cp[i][0])
 
        return maxLeft, maxRight
 

	
 
    def setPoints(self, updates):
 
        for i, pt in updates:
 
            self.curve.points[i] = pt
 
        self.curve.set_points(updates)
 
        self.update_curve()
 
        
 
    def selectionChanged(self):
 
        if self.selectManip:
 
            self.selectManip.update()
 

	
 
    def select_press(self,ev):
 
        # todo: these select_ handlers are getting called on c-a-drag
 
        # zooms too. the dispatching should be more selective than
 
        # just calling both handlers all the time
 
        self.print_state("select_press")
 
        if self.dragging_dots:
 
@@ -660,31 +674,30 @@ class Curveview(object):
 
        
 
    def update_curve(self, *args):
 
        if not self.redrawsEnabled:
 
            return
 

	
 
        if not self.canvasIsVisible():
 
            self.culled = True
 
            return
 
        self.culled = False
 
        
 
        self.size = self.canvas.get_allocation()
 
 
 
        cp = self.curve.points
 
        visible_x = (self.world_from_screen(0,0)[0],
 
                     self.world_from_screen(self.size.width, 0)[0])
 

	
 
        visible_idxs = self.curve.indices_between(visible_x[0], visible_x[1],
 
                                                  beyond=1)
 
        visible_points = [cp[i] for i in visible_idxs]
 
        visible_points = [self.curve.points[i] for i in visible_idxs]
 

	
 
        if getattr(self, 'curveGroup', None):
 
            self.curveGroup.remove()
 
        self.curveGroup = goocanvas.Group(parent=self.root)
 

	
 
        # this makes gtk quietly stop working. Getting called too early?
 
        #self.canvas.set_property("background-color",
 
        #                         "gray20" if self.curve.muted else "black")
 

	
 
        self.update_time_bar(self._time)
 
        if self.size.height < 40:
 
            self._draw_line(visible_points, area=True)
 
@@ -865,25 +878,25 @@ class Curveview(object):
 
    def add_point(self, p):
 
        self.add_points([p])
 

	
 
    def add_marker(self, p):
 
        self.markers.insert_pt(p)
 
        self.update_curve()
 
        
 
    def remove_point_idx(self, *idxs):
 
        idxs = list(idxs)
 
        while idxs:
 
            i = idxs.pop()
 

	
 
            self.curve.points.pop(i)
 
            self.curve.pop_point(i)
 
            newsel = []
 
            newidxs = []
 
            for si in range(len(self.selected_points)):
 
                sp = self.selected_points[si]
 
                if sp == i:
 
                    continue
 
                if sp > i:
 
                    sp -= 1
 
                newsel.append(sp)
 
            for ii in range(len(idxs)):
 
                if ii > i:
 
                    ii -= 1
 
@@ -927,49 +940,61 @@ class Curveview(object):
 
        self.lastMouseX = event.x
 

	
 
        if event.state & gtk.gdk.SHIFT_MASK and 1: # and B1
 
            self.sketch_motion(event)
 
            return
 

	
 
        self.select_motion(event)
 
        
 
        if not self.dragging_dots:
 
            return
 
        if not event.state & 256:
 
            return # not lmb-down
 
        cp = self.curve.points
 
        moved=0
 

	
 
        # this way is accumulating error and also making it harder to
 
        # undo (e.g. if the user moves far out of the window or
 
        # presses esc or something). Instead, we should be resetting
 
        # the points to their start pos plus our total offset.
 
        cur = self.world_from_screen(event.x, event.y)
 
        if self.last_mouse_world:
 
            delta = (cur[0] - self.last_mouse_world[0],
 
                     cur[1] - self.last_mouse_world[1])
 
        else:
 
            delta = 0,0
 
        self.last_mouse_world = cur
 
        
 
        moved = self.translate_points(delta)
 
        
 
        if moved:
 
            self.update_curve()
 

	
 
    def translate_points(self, delta):
 
        moved = False
 
        
 
        cp = self.curve.points
 
        updates = []
 
        for idx in self.selected_points:
 

	
 
            newp = [cp[idx][0] + delta[0], cp[idx][1] + delta[1]]
 
            
 
            newp[1] = max(0,min(1,newp[1]))
 
            
 
            if idx>0 and newp[0] <= cp[idx-1][0]:
 
                continue
 
            if idx<len(cp)-1 and newp[0] >= cp[idx+1][0]:
 
                continue
 
            moved=1
 
            cp[idx] = tuple(newp)
 
        if moved:
 
            self.update_curve()
 
            moved = True
 
            updates.append((idx, tuple(newp)))
 
        self.curve.set_points(updates)
 
        return moved
 

	
 
    def unselect(self):
 
        self.select_indices([])
 

	
 
    def onScroll(self, widget, event):
 
        t = self.world_from_screen(event.x, 0)[0]
 
        self.zoomControl.zoom_about_mouse(
 
            t, factor=1.5 if event.direction == gtk.gdk.SCROLL_DOWN else 1/1.5)
 
        
 
    def onRelease(self, widget, event):
 
        self.print_state("dotrelease")
 

	
0 comments (0 inline, 0 general)