Blender-like grid

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

pablogil wrote:Awesome!
It works perfectly and I love it :D
Thanks. my pleasure.
pablogil wrote:toggling visibility off/on doesn't work, it is always visible... is that a little bug?
Yes it is.
Fixed here :

Code: Select all

import FreeCAD
import FreeCADGui
import math
from pivy import coin

class gridNode(coin.SoSeparator):
    def __init__(self):
        super(gridNode, self).__init__()

        self.vec = coin.SoTransformVec3f()
        #self.vec.matrix.connectFrom(cam.orientation)
        self.vec.vector = coin.SbVec3f(0,0,-1)

        self.calc = coin.SoCalculator()
        self.calc.A.connectFrom(self.vec.direction)
        self.calc.expression.set1Value(0,"ta=0.5") # maxviz
        self.calc.expression.set1Value(1,"tb=20.0") # factor
        self.calc.expression.set1Value(2,"tA=vec3f(1,0,0)") # plane normal
        self.calc.expression.set1Value(3,"tc=dot(A,tA)")
        self.calc.expression.set1Value(4,"td=fabs(tc)")
        self.calc.expression.set1Value(5,"oa=1.0-ta*pow(td,tb)")
        self.calc.expression.set1Value(6,"oA=vec3f(oa,0,0)")

        self.material1 = coin.SoMaterial()
        self.material2 = coin.SoMaterial()
        self.material3 = coin.SoMaterial()
        self.coord = coin.SoCoordinate3()
        self.line1 = coin.SoIndexedLineSet()
        self.line2 = coin.SoIndexedLineSet()
        self.lineSet = coin.SoIndexedLineSet()

        self.addChild(self.coord)
        self.addChild(self.material1)
        self.addChild(self.line1)
        self.addChild(self.material2)
        self.addChild(self.line2)
        self.addChild(self.material3)
        self.addChild(self.lineSet)
       
        self._vector1 = coin.SbVec3f(1,0,0)
        self._vector2 = coin.SbVec3f(0,1,0)
        self.normal = self._vector1.cross(self._vector2)

        self._mainDim = 100
        self._subDim = 10
        self._maxviz = 1.0
        self._factor = 1.0
       
        self._numGridLines = 4
        self.material1.diffuseColor = coin.SbColor(1,0,0)
        self.material2.diffuseColor = coin.SbColor(0,1,0)
        self.material3.diffuseColor = coin.SbColor(0.5,0.5,0.5)
        self.material3.transparency.connectFrom(self.calc.oa)

    @property
    def transparency(self):
        return self.material3.transparency.getValues()[0]

    @transparency.setter
    def transparency(self, tr):
#        self.material3.transparency = tr
#        self.material2.transparency = tr
        self.material3.transparency = tr

    @property
    def vector1color(self):
        return self.material1.diffuseColor.getValues()[0].getValue()

    @vector1color.setter
    def vector1color(self, color):
        self.material1.diffuseColor = (color[0], color[1], color[2])

    @property
    def vector2color(self):
        return self.material2.diffuseColor.getValues()[0].getValue()

    @vector2color.setter
    def vector2color(self, color):
        self.material2.diffuseColor = (color[0], color[1], color[2])

    @property
    def gridcolor(self):
        return self.material3.diffuseColor.getValues()[0].getValue()

    @gridcolor.setter
    def gridcolor(self, color):
        self.material3.diffuseColor = (color[0], color[1], color[2])

    @property
    def vector1dir(self):
        return self._vector1.getValue()

    @vector1dir.setter
    def vector1dir(self, vec):
        self._vector1 = coin.SbVec3f(vec)
        self.normal = self._vector1.cross(self._vector2)
        self.calc.expression.set1Value(2,"tA=vec3f(%f,%f,%f)"%(self.normal.getValue()[0],self.normal.getValue()[1],self.normal.getValue()[2]))
        self.buildGrid()

    @property
    def vector2dir(self):
        return self._vector2.getValue()

    @vector2dir.setter
    def vector2dir(self, vec):
        self._vector2 = coin.SbVec3f(vec)
        self.normal = self._vector1.cross(self._vector2)
        self.calc.expression.set1Value(2,"tA=vec3f(%f,%f,%f)"%(self.normal.getValue()[0],self.normal.getValue()[1],self.normal.getValue()[2]))
        self.buildGrid()

    @property
    def mainDim(self):
        return self._mainDim

    @mainDim.setter
    def mainDim(self, n):
        self._mainDim = n
        self.buildGrid()
       
    @property
    def subDim(self):
        return self._subDim

    @subDim.setter
    def subDim(self, n):
        self._subDim = n
        self.buildGrid()

    @property
    def maxviz(self):
        return self._maxviz

    @maxviz.setter
    def maxviz(self, n):
        self._maxviz = n
        self.calc.expression.set1Value(0,"ta=%f"%n) # maxviz

    @property
    def factor(self):
        return self._factor

    @factor.setter
    def factor(self, n):
        self._factor = n
        self.calc.expression.set1Value(1,"tb=%f"%n) # factor

    def linkTo(self, cam):
        self.vec.matrix.connectFrom(cam.orientation)

    def buildGrid(self):
        n = int(1.0 * self._mainDim / self._subDim)
        r = []
        nr = []
        for i in range(1,n):
            r.append(  1.0 * self._subDim * i)
            nr.append(-1.0 * self._subDim * i)
        r.append(  self._mainDim)
        nr.append(-self._mainDim)
        nr.reverse()
        fullRange = nr + r
        pts = []
        pts.append(-self._mainDim * self._vector1)
        pts.append( self._mainDim * self._vector1)
        pts.append(-self._mainDim * self._vector2)
        pts.append( self._mainDim * self._vector2)
        for i in fullRange:
            pts.append(i * self._vector2 - self._mainDim * self._vector1)
            pts.append(i * self._vector2 + self._mainDim * self._vector1)
            pts.append(i * self._vector1 - self._mainDim * self._vector2)
            pts.append(i * self._vector1 + self._mainDim * self._vector2)
        self.coord.point.setValues(0,len(pts),pts)
        self._numGridLines = len(fullRange) * 2
        #self.gridcolor = self.gridcolor
        #self.transparency = self.transparency
        a = []
        l = len(pts)-4
        for i in range(l/2):
            a.append(2*i + 4)
            a.append(2*i + 5)
            a.append(-1)
        self.line1.coordIndex.setValue(0)
        self.line1.coordIndex.setValues(0, 3, [0,1,-1])
        self.line2.coordIndex.setValue(0)
        self.line2.coordIndex.setValues(0, 3, [2,3,-1])
        self.lineSet.coordIndex.setValue(0)
        self.lineSet.coordIndex.setValues(0, len(a), a)


class gridObject:
    def __init__(self, obj):
        obj.Proxy = self

class gridVP:
    def __init__(self, obj ):
        obj.addProperty("App::PropertyDistance",  "Total",         "Size",   "Size of a grid quadrant").Total = '100mm'
        obj.addProperty("App::PropertyDistance",  "Subdivision",   "Size",   "Size of subdivisions").Subdivision = '10mm'
        obj.addProperty("App::PropertyFloat",     "XY_Attenuation", "View",   "XY plane attenuation").XY_Attenuation = 1.0
        obj.addProperty("App::PropertyFloat",     "XZ_Attenuation", "View",   "XZ plane attenuation").XZ_Attenuation = 50.0
        obj.addProperty("App::PropertyFloat",     "YZ_Attenuation", "View",   "YZ plane attenuation").YZ_Attenuation = 50.0
        obj.addProperty("App::PropertyFloat",     "XY_Visibility",  "View",   "XY plane max visibility").XY_Visibility = 1.0
        obj.addProperty("App::PropertyFloat",     "XZ_Visibility",  "View",   "XZ plane max visibility").XZ_Visibility = 0.5
        obj.addProperty("App::PropertyFloat",     "YZ_Visibility",  "View",   "YZ plane max visibility").YZ_Visibility = 0.5
        obj.addProperty("App::PropertyColor",     "GridColor",     "Color",  "Grid Color").GridColor = (0.5,0.5,0.5)
        obj.Proxy = self

    def attach(self, obj):
        self.xy = gridNode()
        self.xy.vector1dir = (1,0,0)
        self.xy.vector1color = (1,0,0)
        self.xy.vector2dir = (0,1,0)
        self.xy.vector2color = (0,1,0)
        self.xy.mainDim = 100
        self.xy.subDim = 10
        self.xy.maxviz = 1.0
   
        self.xz = gridNode()
        self.xz.vector1dir = (1,0,0)
        self.xz.vector1color = (1,0,0)
        self.xz.vector2dir = (0,0,1)
        self.xz.vector2color = (0,0,1)
        self.xz.mainDim = 100
        self.xz.subDim = 10
        self.xz.maxviz = 0.5
   
        self.yz = gridNode()
        self.yz.vector1dir = (0,1,0)
        self.yz.vector1color = (0,1,0)
        self.yz.vector2dir = (0,0,1)
        self.yz.vector2color = (0,0,1)
        self.yz.mainDim = 100
        self.yz.subDim = 10
        self.yz.maxviz = 0.5
   
        self.sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
        self.cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
   
        self.xy.linkTo(self.cam)
        self.xy.factor = 1.
        self.xz.linkTo(self.cam)
        self.xz.factor = 50.
        self.yz.linkTo(self.cam)
        self.yz.factor = 50.

        self.grid = coin.SoSeparator()

        self.grid.addChild(self.xy)
        self.grid.addChild(self.xz)
        self.grid.addChild(self.yz)
        obj.addDisplayMode(self.grid,"Wireframe")

    def updateCam(self):
        self.cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
        self.xy.linkTo(self.cam)
        self.xz.linkTo(self.cam)
        self.yz.linkTo(self.cam)

    def getDisplayModes(self,obj):
         "Return a list of display modes."
         modes=[]
         modes.append("Wireframe")
         return modes

    def getDefaultDisplayMode(self):
         "Return the name of the default display mode. It must be defined in getDisplayModes."
         return "Wireframe"

    def setDisplayMode(self,mode):
         return mode

    def updateCam(self):
        self.cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
        self.xy.linkTo(self.cam)
        self.xz.linkTo(self.cam)
        self.yz.linkTo(self.cam)

    def onChanged(self, vp, prop):
        self.updateCam()
        if prop == 'Total':
            if float(vp.Total) >= float(vp.Subdivision):
                self.xy.mainDim = float(vp.Total)
                self.xz.mainDim = float(vp.Total)
                self.yz.mainDim = float(vp.Total)
            else:
                vp.Total = vp.Subdivision
        if prop == 'Subdivision':
            if float(vp.Total) >= float(vp.Subdivision):
                self.xy.subDim = float(vp.Subdivision)
                self.xz.subDim = float(vp.Subdivision)
                self.yz.subDim = float(vp.Subdivision)
            else:
                vp.Subdivision = vp.Total
        if prop == 'XY_Attenuation':
            if vp.XY_Attenuation < 0.1:
                vp.XY_Attenuation = 0.1
            elif vp.XY_Attenuation > 100:
                vp.XY_Attenuation = 100
            self.xy.factor = vp.XY_Attenuation
        if prop == 'XZ_Attenuation':
            if vp.XZ_Attenuation < 0.1:
                vp.XZ_Attenuation = 0.1
            elif vp.XZ_Attenuation > 100:
                vp.XZ_Attenuation = 100
            self.xz.factor = vp.XZ_Attenuation
        if prop == 'YZ_Attenuation':
            if vp.YZ_Attenuation < 0.1:
                vp.YZ_Attenuation = 0.1
            elif vp.YZ_Attenuation > 100:
                vp.YZ_Attenuation = 100
            self.yz.factor = vp.YZ_Attenuation
        if prop == 'XY_Visibility':
            if vp.XY_Visibility < 0.0:
                vp.XY_Visibility = 0.0
            elif vp.XY_Visibility > 1.0:
                vp.XY_Visibility = 1.0
            self.xy.maxviz = vp.XY_Visibility
        if prop == 'XZ_Visibility':
            if vp.XZ_Visibility < 0.0:
                vp.XZ_Visibility = 0.0
            elif vp.XZ_Visibility > 1.0:
                vp.XZ_Visibility = 1.0
            self.xz.maxviz = vp.XZ_Visibility
        if prop == 'YZ_Visibility':
            if vp.YZ_Visibility < 0.0:
                vp.YZ_Visibility = 0.0
            elif vp.YZ_Visibility > 1.0:
                vp.YZ_Visibility = 1.0
            self.yz.maxviz = vp.YZ_Visibility
        if prop == 'GridColor':
            self.xy.gridcolor = vp.GridColor
            self.xz.gridcolor = vp.GridColor
            self.yz.gridcolor = vp.GridColor

    def onDelete(self, feature, subelements):
        self.sg.removeChild(self.grid)
        return(True)

def main():

    obj=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Grid")
    gridObject(obj)
    gridVP(obj.ViewObject)


if __name__ == '__main__':
    main()

User avatar
microelly2
Veteran
Posts: 4688
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: Blender-like grid

Post by microelly2 »

Nice, I put it into my toolbox.
Feature request: can you add a Placement property?
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

microelly2 wrote:Nice, I put it into my toolbox.
Thanks.
microelly2 wrote:Feature request: can you add a Placement property?
I'll look at it.
User avatar
pablogil
Posts: 882
Joined: Wed Nov 26, 2014 3:19 pm
Location: Badajoz (Spain)
Contact:

Re: Blender-like grid

Post by pablogil »

I have tested the last version and tweaked it a bit and it works perfectly for me:
colors.png
colors.png (57 KiB) Viewed 1848 times
In case you like the colors I used instead of pure RGB ones here you have the values:

Code: Select all

        self.xy = gridNode()
        self.xy.vector1dir = (1,0,0)
        self.xy.vector1color = (0.827,0.149,0.149) # red (X)
        self.xy.vector2dir = (0,1,0)
        self.xy.vector2color = (0.600,0.970,0.200) # green (Y)
        self.xy.mainDim = 100
        self.xy.subDim = 10
        self.xy.maxviz = 1.0
   
        self.xz = gridNode()
        self.xz.vector1dir = (1,0,0)
        self.xz.vector1color = (0.827,0.149,0.149) # red (X)
        self.xz.vector2dir = (0,0,1)
        self.xz.vector2color = (0.133,0.490,0.882) # blue (Z)
        self.xz.mainDim = 100
        self.xz.subDim = 10
        self.xz.maxviz = 0.5
   
        self.yz = gridNode()
        self.yz.vector1dir = (0,1,0)
        self.yz.vector1color = (0.600,0.970,0.200) # green (Y)
        self.yz.vector2dir = (0,0,1)
        self.yz.vector2color = (0.133,0.490,0.882) # blue (Z)
        self.yz.mainDim = 100
        self.yz.subDim = 10
        self.yz.maxviz = 0.5
One last small thing I would like to see is changing the default "feature" icon with other that fits better... I would suggest the following:
icon.png
icon.png (8.52 KiB) Viewed 1848 times
But as said, it's a great macro! actually, I would like to see it included into master as a replacement of "View / axis cross" or as an additional View feature. ;)

Thank you!
Cheers
Dark and Light stylesheets v2.0 to theme your FreeCAD UI, more information here
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

Hi,
pablogil wrote:In case you like the colors I used instead of pure RGB ones here you have the values:
Thanks for the colors. Indeed, pure RGB is pretty flashy.
pablogil wrote:One last small thing I would like to see is changing the default "feature" icon with other that fits better... I would suggest the following:
OK. I'll look at this.
pablogil wrote:But as said, it's a great macro! actually, I would like to see it included into master as a replacement of "View / axis cross" or as an additional View feature. ;)
Unfortunately, I don't feel I'm able to put anything directly inside FreeCAD master. I am lost in the source code, most of it is C++, and I am too afraid of breaking things and bringing bugs.
Also, be aware that there is something I probably won't be able to fix : if you save a document with a grid object. The grid object will be broken on reload. You'll need to delete it and call the macro again ... In fact, that kind of tool has probably nothing to do in the document tree. But this way offers easy control with properties.
User avatar
pablogil
Posts: 882
Joined: Wed Nov 26, 2014 3:19 pm
Location: Badajoz (Spain)
Contact:

Re: Blender-like grid

Post by pablogil »

Chris_G wrote:Thanks for the colors. Indeed, pure RGB is pretty flashy.
You are welcome, actually, here you have a new version with an even more tweaked green color:

Code: Select all

        self.xy = gridNode()
        self.xy.vector1dir = (1,0,0)
        self.xy.vector1color = (0.827,0.149,0.149) # red (X)
        self.xy.vector2dir = (0,1,0)
        self.xy.vector2color = (0.400,0.590,0.200) # green (Y)
        self.xy.mainDim = 100
        self.xy.subDim = 10
        self.xy.maxviz = 1.0
   
        self.xz = gridNode()
        self.xz.vector1dir = (1,0,0)
        self.xz.vector1color = (0.827,0.149,0.149) # red (X)
        self.xz.vector2dir = (0,0,1)
        self.xz.vector2color = (0.133,0.490,0.882) # blue (Z)
        self.xz.mainDim = 100
        self.xz.subDim = 10
        self.xz.maxviz = 0.5
   
        self.yz = gridNode()
        self.yz.vector1dir = (0,1,0)
        self.yz.vector1color = (0.400,0.590,0.200) # green (Y)
        self.yz.vector2dir = (0,0,1)
        self.yz.vector2color = (0.133,0.490,0.882) # blue (Z)
        self.yz.mainDim = 100
        self.yz.subDim = 10
        self.yz.maxviz = 0.5
Chris_G wrote:Unfortunately, I don't feel I'm able to put anything directly inside FreeCAD master. I am lost in the source code, most of it is C++, and I am too afraid of breaking things and bringing bugs.
Also, be aware that there is something I probably won't be able to fix : if you save a document with a grid object. The grid object will be broken on reload. You'll need to delete it and call the macro again ... In fact, that kind of tool has probably nothing to do in the document tree. But this way offers easy control with properties.
I understand, anyway, I was referring to not place the grid as an object but as a FreeCAD "helper" or gimbal, ala "Axis cross".
Well, maybe somebody else would like to port it to C++... who knows :D

Thank you
Dark and Light stylesheets v2.0 to theme your FreeCAD UI, more information here
User avatar
yorik
Founder
Posts: 13665
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Blender-like grid

Post by yorik »

There is no real need to port it to C++, it can stay in python. Actually it works well as a macro, it's just a matter of naming it with the .py extension instead of .FCMacro, then python is able to import it with "import mymacro.py", and recreate the object when you open a file containing a grid object.

One thing that would really make it a killer feature, would be to make it expand to the screen limits when viewing from an ortho direction, like in Blender... I wonder if coin can give us the size of the viewer window...

Very nice feature!
User avatar
PrzemoF
Veteran
Posts: 3520
Joined: Fri Jul 25, 2014 4:52 pm
Contact:

Re: Blender-like grid

Post by PrzemoF »

I like it too! It should be included in the master IMHO.
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: Blender-like grid

Post by easyw-fc »

very nice indeed! :D
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

yorik wrote:Actually it works well as a macro, it's just a matter of naming it with the .py extension instead of .FCMacro, then python is able to import it with "import mymacro.py", and recreate the object when you open a file containing a grid object.
Hi Yorik,
I'm afraid I don't fully understand this. Who would do the "import mymacro.py" and in which file ? Because I guess that FC needs to know (import) the "blenderGrid.py" BEFORE a file containing such a grid is loaded. Sorry, this is probably a dumb question, but I have quite some difficulties understanding how FC works with the custom featurePython objects :oops:
yorik wrote:One thing that would really make it a killer feature, would be to make it expand to the screen limits when viewing from an ortho direction, like in Blender... I wonder if coin can give us the size of the viewer window...
I am sure it is possible to get this information from coin. I don't know how, yet. In fact, this is the 2 dimensions displayed in the far lower-right corner of FC window, and I guess these numbers come from coin.
Then, how to make the grid size infinitely expanding like in Blender, is another interesting question ...
Post Reply