SOLVED Easier way of getting all selected contour points



  • Hellow!

    In RF if the user selects the on-curve point, the off-curve is implicitly selected too, but is not returned in c.selectedPoints. One workaround is to convert the RPoint to RBPoint which is throwing traceback sometimes. c.selectedBPoints doesn't return the off curve if I've only selected the off curve. Is there an ultimate way to get all the selected points (implicit/explicit) without jumping through hoops?

    Thanks



  • hello @bahman @ro-hernandezz

    the try/except is not really needed, I just forgot to wrap the next point index in parenthesis before performing the modulo division.

    this:

    nextSegment = contour[i + 1 % len(contour.segments)]
    

    must be:

    nextSegment = contour[(i + 1) % len(contour.segments)]
    

    here’s the complete function again and an illustration of what it does, in case someone needs it in the future.

    magenta = selected points / blue = implicit selected points

    def getImplicitSelectedPoints(glyph):
        pts = []
        for contour in glyph.contours:
            for i, segment in enumerate(contour.segments):
                for pt in segment:
                    if not pt.selected:
                        continue
                    pts.append(pt)
                    # implicit == include BCPs in selection
                    if pt.type != 'offcurve':
                        # bcpIn
                        if len(segment) == 3:
                            bcpIn = segment[-2]
                            pts.append(bcpIn)
                        # bcpOut
                        nextSegment = contour[(i + 1) % len(contour.segments)]
                        if len(nextSegment) == 3:
                            bcpOut = nextSegment[0]
                            pts.append(bcpOut)
        return pts
        
    # visualize it with the DrawBot extension
    
    g = CurrentGlyph()
    size(700, 700)
    translate(30, 100)
    fill(None)
    strokeWidth(4)
    stroke(0, 1, 1)
    drawGlyph(g)
    
    fill(0, 0, 1)
    stroke(None)
    r = 10
    for pt in getImplicitSelectedPoints(g):    
        oval(pt.x - r, pt.y - r, r * 2, r * 2)
    
    fill(None)
    stroke(1, 0, 1)    
    r += 5
    for pt in g.selectedPoints:
        oval(pt.x - r, pt.y - r, r * 2, r * 2)
    


  • @bahman said in Easier way of getting all selected contour points:

    def getImplicitSelectedPoints(glyph):
        pts = []
        for contour in glyph.contours:
            for i, segment in enumerate(contour.segments):
                for pt in segment:
                    if not pt.selected:
                        continue
                    pts.append(pt)
                    # implicit == include BCPs in selection
                    if pt.type != 'offcurve':
                        # bcpIn
                        if len(segment) == 3:
                            bcpIn = segment[-2]
                            pts.append(bcpIn)
                        # bcpOut
                        try:
                            nextSegment = contour[i + 1]
                        except IndexError:
                            nextSegment = contour[0]
                        if len(nextSegment) == 3:
                            bcpOut = nextSegment[0]
                            pts.append(bcpOut)
        return pts
    

    Thanks @gferreira and @bahman! I'd been trying to figure this out all morning. That'll teach me to search the forums first (:



  • I used this in the following script, which rounds coordinates of elements inside glyph using my own method. It works on selected elements. I put snap to zero in RF preferences so I get floating-point coordinates. So I run this when I want to finish the work:

    from fontTools.pens.t2CharStringPen import t2c_round
    from fontTools.misc.fixedTools import otRound
    
    TOLERANCE = 0.5
    
    
    def roundElement(e, tolerance=TOLERANCE):
    	old_x, old_y = e.x, e.y
    	e.x = t2c_round(e.x, tolerance)
    	e.y = t2c_round(e.y, tolerance)
    	if old_x != e.x or old_y != e.y:
    		return True
    	return False
    
    
    def scaleRound(value):
    	"""
    	used to round component scales to 2 decimal points.
    	"""
    	return round(value, 2)
    
    
    def getSelectedPoints(glyph):
    	pts = []
    	for contour in glyph.contours:
    		for i, segment in enumerate(contour.segments):
    			for pt in segment:
    				if not pt.selected:
    					continue
    				pts.append(pt)
    				# implicit == include BCPs in selection
    				if pt.type != 'offcurve':
    					# bcpIn
    					if len(segment) == 3:
    						bcpIn = segment[-2]
    						pts.append(bcpIn)
    					# bcpOut
    					try:
    						nextSegment = contour[i + 1]
    					except IndexError:
    						nextSegment = contour[0]
    					if len(nextSegment) == 3:
    						bcpOut = nextSegment[0]
    						pts.append(bcpOut)
    	return pts
    
    
    def roundGlyph(g, markGlyph=True):
    	"""
    	Round selected objects coordinates in a given glyph.
    	If nothing is selected, round all the elements insde glyph.
    	"""
    	g.prepareUndo("Round coordinates to integer")
    	if g.contours != () or g.anchors != () or g.components != ():
    		fixedState = set()
    		selectedPoints = g.selectedPoints
    		selectedAnchors = g.selectedAnchors
    		selectedComponents = g.selectedComponents
    		hasSelection = True in [selection != () for selection in [selectedPoints, selectedAnchors, selectedComponents]]
    		if hasSelection:
    			fixedState.update(map(roundElement, getSelectedPoints(g)))
    		else:
    			for c in g.contours:
    				fixedState.update(map(roundElement, c.points))
    		if True in fixedState:
    			fixedState.clear()
    			fixedState.add("Points")
    		else:
    			fixedState.clear()
    
    		for c in g.components:
    			if hasSelection and c not in selectedComponents:
    				continue
    			xScale, xyScale, yxScale, yScale, xOffset, yOffset = c.transformation
    			new_transformation = (scaleRound(xScale), scaleRound(xyScale), scaleRound(yxScale), scaleRound(yScale), otRound(xOffset), otRound(yOffset))
    			if new_transformation != c.transformation:
    				c.transformation = new_transformation
    				fixedState.add("Components")
    		for a in g.anchors:
    			if hasSelection and a not in selectedAnchors:
    				continue
    			if roundElement(a) is True:
    				fixedState.add("Anchros")
    
    	g_width = g.width
    	g_width_rounded = otRound(g_width)
    	if g_width != g_width_rounded:
    		g.width = g_width_rounded
    		fixedState.add("Width")
    
    	if fixedState != set():
    		print('%s fixed: %s' % (g.name, "\n".join(sorted(fixedState))))
    		if markGlyph is True:
    			g.markColor = (0.1, 0.8, 0.2, 1)
    		g.changed()
    	g.performUndo()
    
    
    if __name__ == '__main__':
    	from mojo.UI import *
    	OutputWindow().clear()
    
    	f = CurrentFont()
    	cg = CurrentGlyph()
    	glist = []
    	if len(f.templateSelectedGlyphNames) < 2 and cg is not None:
    		roundGlyph(cg, False)
    	else:
    		for gl in f.selectedGlyphNames:
    			g = f[gl]
    			roundGlyph(g)
    
    


  • Ok I fixed it using a try and except, I hope it's not incorrect. Cheers!

    def getImplicitSelectedPoints(glyph):
        pts = []
        for contour in glyph.contours:
            for i, segment in enumerate(contour.segments):
                for pt in segment:
                    if not pt.selected:
                        continue
                    pts.append(pt)
                    # implicit == include BCPs in selection
                    if pt.type != 'offcurve':
                        # bcpIn
                        if len(segment) == 3:
                            bcpIn = segment[-2]
                            pts.append(bcpIn)
                        # bcpOut
                        try:
                            nextSegment = contour[i + 1]
                        except IndexError:
                            nextSegment = contour[0]
                        if len(nextSegment) == 3:
                            bcpOut = nextSegment[0]
                            pts.append(bcpOut)
        return pts
    


  • Thank you @gferreira this works very well, except on a start point. I'm gonna see how to fix that.



  • hello @bahman,

    I see what you mean: the implicit offcurve selection is just a visual aid; to add the offcurve points to the selection you have to explicitly click on them. (I think that makes sense.)

    to get the implicit offcurve points in code, you can use segments:

    def getImplicitSelectedPoints(glyph):
        pts = []
        for contour in glyph.contours:
            for i, segment in enumerate(contour.segments):
                for pt in segment:
                    if not pt.selected:
                        continue
                    pts.append(pt)
                    # implicit == include BCPs in selection
                    if pt.type != 'offcurve':
                        # bcpIn
                        if len(segment) == 3:
                            bcpIn = segment[-2]
                            pts.append(bcpIn)
                        # bcpOut
                        nextSegment = contour[i + 1 % len(contour.segments)]
                        if len(nextSegment) == 3:
                            bcpOut = nextSegment[0]
                            pts.append(bcpOut)
        return pts
    

    please give it a try & let us know if this works for you.

    cheers!


Log in to reply