My first plugin: Show Stem Thickness


#1

Hello! I just learned something about bezier curves, something about Python coding and little bit about Objective - C.
That’s my first attempt in the programming world, and I know that I could made some mistakes etc.
So I asking you, more experienced guys for advices:
how can I improve this plugin (how to improve speed)etc. Maybe I missed something?

Plugin calculates thickness of pointed stem.
General idea (how it works):

  1. Plugin calculates closest point on curve (in relation to current cursor’s coordinates)
  2. if distance from “point on curve” to cursor is less than some value:
  3. plugin calculates path, perpendicular to “point on curve”.
  4. on this path, plugin calculates all intersections

what happens next you can see, by trying this plugin


Thanks in advance for help and have please, patience for my programming experiance

PS. This is the beta version of plugin: I think you should create backup file, before using it
Here is the illustration how it looks:


#2

Not sure what the plugin does yet, but I do want to say your name for it is somewhat less than ideal. Try something that describes more what the plugin does. :slight_smile:


#3

Sure, maybe I will try with “Show Stem Thickness”
(the same as in fontlab’s equivalent, I don’t know if there is a legal problem)


#4

Welcome.

I had a look at the code. I don’t fully understand what it does, yet but I can give you some hints:

I changed the foreground method like this:

    def foreground(self, layer):
        import cProfile
        cProfile.runctx('self._foreground(layer)', globals(), locals())
        
    def _foreground(self, layer):
        view = self.controller.graphicView()

That prints an overview of what methods are called and how long they take. It will print something like this in the macro window:


         101710 function calls in 0.351 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.351    0.351 <string>:1(<module>)
        1    0.000    0.000    0.002    0.002 FatBelly.py:113(calcTangent)
        1    0.000    0.000    0.000    0.000 FatBelly.py:145(angle)
        4    0.000    0.000    0.000    0.000 FatBelly.py:160(rotatePoint)
      489    0.001    0.000    0.001    0.000 FatBelly.py:18(distanceAB)
        1    0.000    0.000    0.351    0.351 FatBelly.py:187(_foreground)
      480    0.001    0.000    0.001    0.000 FatBelly.py:28(bezierCurve)
        1    0.000    0.000    0.341    0.341 FatBelly.py:350(calcClosestPtOnCurveAndTangent)
        2    0.000    0.000    0.000    0.000 FatBelly.py:372(<genexpr>)
        8    0.000    0.000    0.000    0.000 FatBelly.py:41(pathAB)
        8    0.012    0.001    0.336    0.042 FatBelly.py:48(getLut)
        1    0.000    0.000    0.001    0.001 FatBelly.py:96(closest)
    19415    0.007    0.000    0.007    0.000 __init__.py:111(__init__)
     1949    0.024    0.000    0.024    0.000 __init__.py:1162(<lambda>)
     1948    0.024    0.000    0.024    0.000 __init__.py:1165(<lambda>)
        1    0.000    0.000    0.000    0.000 __init__.py:1216(<lambda>)
        6    0.000    0.000    0.000    0.000 __init__.py:132(__iter__)
        2    0.000    0.000    0.000    0.000 __init__.py:1848(values)
    15524    0.078    0.000    0.081    0.000 __init__.py:1872(__getitem__)
     4853    0.013    0.000    0.013    0.000 __init__.py:1884(__len__)
        1    0.000    0.000    0.000    0.000 __init__.py:2057(<lambda>)
        1    0.000    0.000    0.000    0.000 __init__.py:2284(Font_selectedLayers)
        1    0.000    0.000    0.000    0.000 __init__.py:2287(<lambda>)
        1    0.010    0.010    0.010    0.010 __init__.py:250(currentFont)
        1    0.000    0.000    0.010    0.010 __init__.py:259(<lambda>)
        2    0.000    0.000    0.000    0.000 __init__.py:4587(<lambda>)
    19413    0.014    0.000    0.021    0.000 __init__.py:5736(<lambda>)
        4    0.000    0.000    0.000    0.000 __init__.py:5765(<lambda>)
     3938    0.046    0.000    0.046    0.000 __init__.py:6044(__GSNode_get_type__)
        1    0.000    0.000    0.000    0.000 _convenience.py:444(__getitem__objectAtIndex_)
        6    0.000    0.000    0.000    0.000 _convenience.py:587(enumeratorGenerator)
        2    0.000    0.000    0.000    0.000 _convenience.py:602(__iter__objectEnumerator_keyEnumerator)
        7    0.000    0.000    0.000    0.000 _convenience.py:657(container_unwrap)
        4    0.000    0.000    0.000    0.000 _pythonify.py:53(__new__)
        4    0.000    0.000    0.000    0.000 _pythonify.py:60(__getattr__)
        4    0.000    0.000    0.000    0.000 _pythonify.py:71(numberWrapper)
       16    0.000    0.000    0.000    0.000 objectsBase.py:2163(__init__)
     3881    0.029    0.000    0.029    0.000 objectsGS.py:41(<lambda>)
       16    0.000    0.000    0.000    0.000 objectsGS.py:792(__init__)
        9    0.000    0.000    0.000    0.000 objectsGS.py:805(__len__)
     3881    0.007    0.000    0.276    0.000 objectsGS.py:810(__getitem__)
     3881    0.077    0.000    0.269    0.000 objectsGS.py:897(_get_points)
        4    0.001    0.000    0.001    0.000 objectsGS.py:945(__GSPath_get_segments)
        1    0.000    0.000    0.000    0.000 plugins.py:1195(getHandleSize)
        1    0.000    0.000    0.000    0.000 plugins.py:1213(getScale)
        4    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x100acf408}
        2    0.000    0.000    0.000    0.000 {built-in method from_iterable}
        6    0.000    0.000    0.000    0.000 {getattr}
        6    0.000    0.000    0.000    0.000 {isinstance}
        2    0.000    0.000    0.000    0.000 {iter}
     4863    0.003    0.000    0.017    0.000 {len}
        1    0.000    0.000    0.000    0.000 {math.atan}
        9    0.000    0.000    0.000    0.000 {math.cos}
        4    0.000    0.000    0.000    0.000 {math.radians}
        9    0.000    0.000    0.000    0.000 {math.sin}
      489    0.000    0.000    0.000    0.000 {math.sqrt}
    16524    0.003    0.000    0.003    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'index' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {next}
        5    0.000    0.000    0.000    0.000 {range}
        8    0.000    0.000    0.000    0.000 {round}

the interesting part is the cumtime column. It shows that the _foreground method is taking 0.351 s and the getLUT method uses almost all that time (0.336 s).

Here are some tips:

don’t use path.segments. iterate over the nodes and check the node.type if it is CURVE or LINE and then use path.nodes[ i - 1 ] to get to the previous nodes.

There is a method path.nearestPointOnPath_pathTime_(location, None) that is quite fast.


#5

I improved the performance a bit but messed it up, too. But I hope my changed will give you some inside.Belly Fat.glyphsReporter.zip (28.0 KB)


#6

Thanks for great respond, Georg.
I just have one question: it is better to iterate over the nodes. because segments are heavier for plugin to compute? or there is some other reason.

I can see, that the biggest problem of that plugin acured after your changes:
indexing layer.intersectionsBetweenPoints(resultPoints[‘normal’],resultPoints[‘onCurve’])
I didn’t get how it behaved, so I forced it to behave like I wanted to, by placing if-statements.


#7

Yes. It can be slow if you iterate the segments often. In the Heatmap plugin from Simon, it made a huge difference.


#8

I uploaded new version, Thanks Georg: it works much better.


#9

Do you plan to add it to the plugin manager?


#10

Do you plan to add it to the plugin manager?

Yes, I will try to figure out how to do it in following days.


#11

New question:
Is there any chance to track position of the cursor, when user is holding the left mouse button (not just clicking)? I couldn’t find solution on my own.


#12

No. That is where you need a tool for. But as you can register for the mouse movement, I should add clicking and dragging, too.


#13

Amazing plugin! Thank you!

There is a problem, which I’ve seen with one or two other plugins too. (Which makes me wonder if it’s actually something Glyphs is not doing properly) - with this reporter on and the Text tool selected, the thickness of the bars between the letters depends on the zoom level. I think you’re not resetting a stroke thickness after drawing a line somewhere, and equally I think Glyphs is not correctly setting the stroke thickness before drawing those boundaries:


#14

Would it be possible to make the Mouse Double Clicking event accessible, too, while you’re at it?
Here is the old post:


#15

This is an issue with some plugins, that set the stroke thickness. For example it occured with the »Show Angled Handles« Plugin, in which Rainer already implemented the solution.


#16

Right. But Glyphs should set it back before it draws something.


#17

The external code runs between code segments that would run just next to each other befor. So I didn’t need to check that all the time. I’ll fix it.


#18

Another, (I hope last question about this plugin):
I have a problem with updating. I’m changing CFBundleShortVersionString. from to 1.1.1 and nothing happens in glyphsApp. I think that my url for updating is proper. Maybe it is problem with my internet connection? (I’m on holidays, in wild place, with poor internet connection)


How can I make it work? I’ve been fighting with it for last two days.


#19

Glyphs notifies you if the version number in the Update URL is higher than the one installed. If you local one is 1.1.1 and the one online 1.1.0, you already have the newer version.


#20

And if the plugin is installed through the Plugin Manager, it is updated automatically if you push something to your repository.