Blender-like grid

Need help, or want to share a macro? Post here!
User avatar
Chris_G
Posts: 1307
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Postby Chris_G » Fri May 22, 2020 5:35 pm

Hi,
It needed a little fix for python 3.

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.scaleEngine = coin.SoCalculator()
        #self.scaleEngine.a.connectFrom(cam.height)
        self.scaleEngine.expression.set1Value(0,"ta=floor(log10(a/10))")
        self.scaleEngine.expression.set1Value(1,"tb=pow(10,ta)")
        self.scaleEngine.expression.set1Value(2,"oA=vec3f(tb,tb,tb)")
        self.scaleEngine.expression.set1Value(3,"oa=0.01*a/tb")

        self.calc2 = coin.SoCalculator()
        self.calc2.a.connectFrom(self.scaleEngine.oa)
        self.calc2.b.connectFrom(self.calc.oa)
        self.calc2.expression.set1Value(0,"ta=pow(a,0.3)")
        self.calc2.expression.set1Value(1,"oa=(b>ta)?b:ta")


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

        self.miniscale = coin.SoScale()
        self.miniscale.scaleFactor = coin.SbVec3f(0.1,0.1,0.1)

        self.mainscale = coin.SoScale()
        self.mainscale.scaleFactor.connectFrom(self.scaleEngine.oA)

        self.addChild(self.mainscale)
        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.addChild(self.miniscale)
        self.addChild(self.material4)
        self.addChild(self.coord2)
        self.addChild(self.lineSet2)
       
        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.material4.diffuseColor = coin.SbColor(0.5,0.5,0.5)
        self.material3.transparency.connectFrom(self.calc.oa)
        self.material4.transparency.connectFrom(self.calc2.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])
        self.material4.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)
        self.scaleEngine.a.connectFrom(cam.height)

    def updateTransformedNormal(self):
#          // First get hold of an SoPath through the scenegraph down to the
#          // node ("mynode") you want to query about its current world space
#          // transformation(s).
#        
#          SoSearchAction * searchaction = new SoSearchAction;
#          searchaction->setNode(mynode);
#          searchaction->apply(myscenegraphroot);
#        
#          SoPath * path = searchaction->getPath();
#          assert(path != NULL);
#        
#          // Then apply the SoGetMatrixAction to get the full transformation
#          // matrix from world space.
#        
#          const SbViewportRegion vpr = myviewer->getViewportRegion();
#          SoGetMatrixAction * getmatrixaction = new SoGetMatrixAction(vpr);
#          getmatrixaction->apply(path);
#        
#          SbMatrix transformation = getmatrixaction->getMatrix();
#        
#          // And if you want to access the individual transformation
#          // components of the matrix:
#        
#          SbVec3f translation;
#          SbRotation rotation;
#          SbVec3f scalevector;
#          SbRotation scaleorientation;
#        
#          transformation.getTransform(translation, rotation, scalevector, scaleorientation);
        searchaction = coin.SoSearchAction()
        searchaction.setNode(self)
        searchaction.apply(FreeCADGui.ActiveDocument.ActiveView.getSceneGraph())
        path = searchaction.getPath()
        vpr = FreeCADGui.ActiveDocument.ActiveView.getViewer().getViewportRegion()
        getmatrixaction = coin.SoGetMatrixAction(vpr)
        getmatrixaction.apply(path)
        transformation = getmatrixaction.getMatrix()
        self.transformedNormal = transformation.multVecMatrix(self.normal)
        self.calc.expression.set1Value(2,"tA=vec3f(%f,%f,%f)"%(self.transformedNormal.getValue()[0],self.transformedNormal.getValue()[1],self.transformedNormal.getValue()[2]))
        return()

    def gridPts(self, t, s):
        n = t*s
        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 = []
        for i in fullRange:
            pts.append(1*i * self._vector2 - s*self._mainDim * self._vector1)
            pts.append(1*i * self._vector2 + s*self._mainDim * self._vector1)
            pts.append(1*i * self._vector1 - s*self._mainDim * self._vector2)
            pts.append(1*i * self._vector1 + s*self._mainDim * self._vector2)
        return(pts)

    def buildGrid(self):
        n = int(1.0 * self._mainDim / self._subDim)
        
        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)
        
        pts += self.gridPts(n,1)
        self.coord.point.setValues(0,len(pts),pts)
        self._numGridLines = len(pts) / 2.0

        pts2 = self.gridPts(n,10)
        self.coord2.point.setValues(0,len(pts2),pts2)
        #self._numGridLines = len(pts) / 2.0

        a = []
        l = len(pts)-4
        for i in range(int(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)

        a2 = []
        l = len(pts2)
        for i in range(int(l/2)):
            a2.append(2*i)
            a2.append(2*i + 1)
            a2.append(-1)
        self.lineSet2.coordIndex.setValue(0)
        self.lineSet2.coordIndex.setValues(0, len(a2), a2)


class gridObject:
    def __init__(self, obj):
        obj.Proxy = self
        obj.addProperty("App::PropertyPlacement",  "Placement",   "Base",   "Placement")
    def execute(self, obj):
        return()
    def onChanged(self, fp, prop):
        if prop == 'Placement':
            FreeCAD.Console.PrintMessage('Placement udpate\n')
            tr = fp.Placement.Base
            ro = fp.Placement.Rotation.Q
            fp.ViewObject.Proxy.trans.translation = coin.SbVec3f(tr.x,tr.y,tr.z)
            fp.ViewObject.Proxy.trans.rotation = coin.SbRotation(ro[0],ro[1],ro[2],ro[3])
    
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 = 2.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.trans = coin.SoTransform()

        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
   
        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.SoGroup()

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

        self.ViewObject = obj
        self.Object = obj.Object

#    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 getIcon(self):
        return (":/icons/Draft_Grid.svg")

    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
        if prop == 'Placement':
            FreeCAD.Console.PrintMessage('Placement udpate\n')
            tr = vp.Object.Placement.Base
            ro = vp.Object.Placement.Rotation.Q
            self.trans.translation = coin.SbVec3f(tr.x,tr.y,tr.z)
            self.trans.rotation = coin.SbRotation(ro[0],ro[1],ro[2],ro[3])
            self.xy.updateTransformedNormal()
            self.xz.updateTransformedNormal()
            self.yz.updateTransformedNormal()


    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
mnesarco
Posts: 92
Joined: Thu Mar 26, 2020 8:52 pm

Re: Blender-like grid

Postby mnesarco » Fri May 22, 2020 5:41 pm

Chris_G wrote:
Fri May 22, 2020 5:35 pm
Hi,
It needed a little fix for python 3.
Hi Chris_G, it runs but look what i get:
FreeCAD 0.19_120.png
FreeCAD 0.19_120.png (229.68 KiB) Viewed 201 times
User avatar
Chris_G
Posts: 1307
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Postby Chris_G » Fri May 22, 2020 5:50 pm

Urgh, not good.
It stays like this, when you move the camera ?
What is your FC info, please ?
User avatar
mnesarco
Posts: 92
Joined: Thu Mar 26, 2020 8:52 pm

Re: Blender-like grid

Postby mnesarco » Fri May 22, 2020 6:42 pm

Chris_G wrote:
Fri May 22, 2020 5:50 pm
Urgh, not good.
It stays like this, when you move the camera ?
What is your FC info, please ?
When I Move camera it goes worse :mrgreen:

OS: Linux Mint 19.2 (X-Cinnamon/cinnamon)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.21125 (Git) AppImage
Build type: Release
Branch: master
Hash: 40600a55c2fe71ff589be677f6e427ccc937d003
Python version: 3.8.2
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: English/United States (en_US)
User avatar
Chris_G
Posts: 1307
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Postby Chris_G » Fri May 22, 2020 8:29 pm

Well ... I don't know what's going wrong.
You don't get any error in the report view ?
Changing the properties in the View Tab of the Grid object has any effect ?
If you look at this video, you'll see the supposed grid's behaviour when aligning the camera to standard X,Y, Z views.
It is working as expected on my PC with the latest Py3 fix.
User avatar
mnesarco
Posts: 92
Joined: Thu Mar 26, 2020 8:52 pm

Re: Blender-like grid

Postby mnesarco » Fri May 22, 2020 9:13 pm

Chris_G wrote:
Fri May 22, 2020 8:29 pm
Well ... I don't know what's going wrong.
You don't get any error in the report view ?
Changing the properties in the View Tab of the Grid object has any effect ?
If you look at this video, you'll see the supposed grid's behaviour when aligning the camera to standard X,Y, Z views.
It is working as expected on my PC with the latest Py3 fix.
No errors in the report window.
carlopav
Posts: 1205
Joined: Mon Dec 31, 2018 1:49 pm
Location: Venice, Italy

Re: Blender-like grid

Postby carlopav » Sat May 23, 2020 9:19 am

Could not resist to try it with:

Code: Select all

OS: Windows 7 SP 1 (6.1)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.20790 +1 (Git)
Build type: Release
Branch: Draft_split_object
Hash: 77d6ad02c8aa3b9c6523386971b9e1d59f8e92ee
Python version: 3.6.8
Qt version: 5.12.1
Coin version: 4.0.0a
OCC version: 7.3.0
Locale: Italian/Italy (it_IT)
Everything seems good:
Cattura.JPG
Cattura.JPG (68.68 KiB) Viewed 123 times
The grid resize itself when zooming out from an ortogonal view and it seems infinite.
It's really nice. Perhaps a nice plus would be to differentiate positive xyz axis from their negative part...
follow my experiments on BIM modelling for architecture design
User avatar
Chris_G
Posts: 1307
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Postby Chris_G » Sat May 23, 2020 9:36 am

carlopav wrote:
Sat May 23, 2020 9:19 am
The grid resize itself when zooming out from an ortogonal view and it seems infinite.
Unfortunately, it only works when camera is pointing at the origin
carlopav wrote:
Sat May 23, 2020 9:19 am
Perhaps a nice plus would be to differentiate positive xyz axis from their negative part...
I agree.
User avatar
HakanSeven12
Posts: 758
Joined: Wed Feb 06, 2019 10:30 pm

Re: Blender-like grid

Postby HakanSeven12 » Sat May 23, 2020 11:10 am

Works for me. I think this can be part of the official release. Thanks.

Code: Select all

OS: Windows 10 (10.0)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.21125 (Git)
Build type: Release
Branch: master
Hash: 40600a55c2fe71ff589be677f6e427ccc937d003
Python version: 3.8.2
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: Turkish/Turkey (tr_TR)

test.png
test.png (165.03 KiB) Viewed 97 times