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
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: Blender-like grid

Post by looo »

You can get the intersection of a screen-position (px, py) with a plane in 3d-space with this code:
I am not sure, but I think (px, py) is in the range of (0, 1) where 0 is left/upper and 1 is right/lower. But could be also the other way.

Code: Select all

from pivy import coin
render_manager = Gui.ActiveView.getViewer().getSoRenderManager()
cam = render_manager.getCamera()
vol = pCam.getViewVolume()
line = coin.SbLine(*vol.projectPointToLine(coin.SbVec2f(pX,pY)))
plane = coin.SbPlane(normal, point)
point = plane.intersect(line)
Doing this for all 4 corner points of the screen and choosing the max/min values should give you the span to draw the grid. This could be updated again with a callbackNode which triggers at zooming or maybe also with a Sensor.

Blender also updates the grid spaces on zooming, another nice feature... ;)
User avatar
yorik
Founder
Posts: 13659
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Blender-like grid

Post by yorik »

Yes, I don't think the Blender grid is really infinite. I think it gets redrawn everytime the camera moves. That's how all openGL tutorials out there tell you to do it...

About the macro / .py thing: It was just a way of saying, it is not totally necessary to create a full FreeCAD module, if you have all the code inside one python file, it can live in the Macros folder too.
User avatar
Chris_G
Veteran
Posts: 2593
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

looo wrote:Doing this for all 4 corner points of the screen and choosing the max/min values should give you the span to draw the grid. This could be updated again with a callbackNode which triggers at zooming or maybe also with a Sensor.
Thanks looo.
I'll just stay away from the sensors for now :lol:
looo wrote:Blender also updates the grid spaces on zooming, another nice feature... ;)
I saw this. I'll look to see if it is difficult to do or not.
yorik wrote:Yes, I don't think the Blender grid is really infinite. I think it gets redrawn everytime the camera moves. That's how all openGL tutorials out there tell you to do it...

About the macro / .py thing: It was just a way of saying, it is not totally necessary to create a full FreeCAD module, if you have all the code inside one python file, it can live in the Macros folder too.
Thanks Yorik.
In case it can be useful to someone, here is an example of a custom SoSwitch that reacts to view direction :

Code: Select all

from pivy import coin

class orthoViewSwitch(coin.SoSwitch):
    def __init__(self, cam = None):
        super(orthoViewSwitch, self).__init__()

        self.vec = coin.SoTransformVec3f()
        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.00001") # tolerance
        self.calc.expression.set1Value(1, "tA=vec3f(0,0,1)") # XY plane normal
        self.calc.expression.set1Value(2, "tB=vec3f(0,1,0)") # XZ plane normal
        self.calc.expression.set1Value(3, "tC=vec3f(1,0,0)") # YZ plane normal
        self.calc.expression.set1Value(4, "oa=fabs(dot(A,tA))") # XY value
        self.calc.expression.set1Value(5, "ob=fabs(dot(A,tB))") # XZ value
        self.calc.expression.set1Value(6, "oc=fabs(dot(A,tC))") # YZ value
        self.calc.expression.set1Value(7, "tb=(oa>ob)?oa:ob")
        self.calc.expression.set1Value(8, "tc=(tb>oc)?tb:oc") # winning value
        self.calc.expression.set1Value(9, "tf=(oa==tc)&&((oa+ta)>1)?1:0")
        self.calc.expression.set1Value(10,"tg=(ob==tc)&&((ob+ta)>1)?2:0")
        self.calc.expression.set1Value(11,"th=(oc==tc)&&((oc+ta)>1)?3:0")
        self.calc.expression.set1Value(12,"od=tf+tg+th") # switch value

        if cam:
            self.connectCamera(cam)
        else:
            self.whichChild = 0

    def connectCamera(self, cam):
        self.vec.matrix.connectFrom(cam.orientation)
        self.whichChild.connectFrom(self.calc.od)
    def disconnect(self):
        self.whichChild = 0
    def setTolerance(self, tol):
        self.calc.expression.set1Value(0,"ta=%f"%tol)




trans = coin.SoTranslation()
trans.translation = (2.0,0,0)

color = coin.SoBaseColor()
color.rgb = (0,0,0)

cube = coin.SoCube()

# One text node in each child of the Switch

no = coin.SoText2()
no.string = "View is not orthogonal to any plane"

xy = coin.SoText2()
xy.string = "View is orthogonal to XY plane"

xz = coin.SoText2()
xz.string = "View is orthogonal to XZ plane"

yz = coin.SoText2()
yz.string = "View is orthogonal to YZ plane"

# --------------

orthoSwitch = orthoViewSwitch()

orthoSwitch.addChild(no)
orthoSwitch.addChild(xy)
orthoSwitch.addChild(xz)
orthoSwitch.addChild(yz)

sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()


sg.addChild(cube)
sg.addChild(color)
sg.addChild(trans)
sg.addChild(orthoSwitch)


cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
# Connect the switch to the camera
orthoSwitch.connectCamera(cam)
# adjust the detection tolerance
orthoSwitch.setTolerance(0.001)

mario52
Veteran
Posts: 4690
Joined: Wed May 16, 2012 2:13 pm

Re: Blender-like grid

Post by mario52 »

hi Chris_G
good project is it possible to replace the axis or include a cube with the axis existant ?

example:
BlenderCube00.png
BlenderCube00.png (1.45 KiB) Viewed 2483 times
Or include one shape in the cube like here for visualise easily the view

mario
Maybe you need a special feature, go into Macros_recipes and Code_snippets, Topological_data_scripting.
My macros on Gist.github here complete macros Wiki and forum.
User avatar
Chris_G
Veteran
Posts: 2593
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Blender-like grid

Post by Chris_G »

@Mario,
I'll put this on the TODO list.

I have experimented with auto-scale ( grid sub-divisions that gradually appear / disappear when zooming in / out ).
Test code below ( with Pablogil's color ;) )
Currently, it only works when zooming in around origin. The grid disappears if zooming on a point away from origin.
But that's a step forward ...

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

mario52
Veteran
Posts: 4690
Joined: Wed May 16, 2012 2:13 pm

Re: Blender-like grid

Post by mario52 »

hi
Chris_G wrote:@Mario,
I'll put this on the TODO list.
thanks
Chris_G wrote:Currently, it only works when zooming in around origin. The grid disappears if zooming on a point away from origin.
I think that the cause is the axes infinite then the Image zoom remote remote ....

mario
Maybe you need a special feature, go into Macros_recipes and Code_snippets, Topological_data_scripting.
My macros on Gist.github here complete macros Wiki and forum.
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:@Mario,
I'll put this on the TODO list.

I have experimented with auto-scale ( grid sub-divisions that gradually appear / disappear when zooming in / out ).
Test code below ( with Pablogil's color ;) )
Currently, it only works when zooming in around origin. The grid disappears if zooming on a point away from origin.
But that's a step forward ...
Working fine here, it's a nice update.
One thing I have seen (that also happens with regular Draft grid) and annoys me: is there a possibility that "zoom all" tool filters the grid so that it doesn't calculate it as part of the project geometry?

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

Re: Blender-like grid

Post by Chris_G »

Hi,
mario52 wrote:I think that the cause is the axes infinite then the Image zoom remote remote ....
pablogil wrote:One thing I have seen (that also happens with regular Draft grid) and annoys me: is there a possibility that "zoom all" tool filters the grid so that it doesn't calculate it as part of the project geometry?
Both problems are similar.
Unfortunately, currently, I don't see any easy way to fix this. The "ViewFitAll" action is done directly with coin, on the whole scenegraph. So all visible objects in the 3D view are used for the computation.
I am facing a contradiction here :
- ViewFitAll computes the general bounding box of 3DView objects and modifies the camera so that it's own ViewVolume contains the bounding box.
- the "infinite" grid, on the other hand, gets the camera viewvolume, and expand itself to be bigger than this viewVolume.
It is the snake biting its own tail ( a commonly used french sentence, I don't know of it is used in other countries ? )
Chris
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: Blender-like grid

Post by triplus »

Having an option to set grid size and above that size for the grid to expand to available geometry size? Could that be implemented? And could Draft Snap functionality be made to work with your grid? For some interactive and not just visual use cases.

P.S. To be honest i am not sure if infinite grid is all that useful. As you work in finite space anyway and there is where you need the grid.
User avatar
mnesarco
Posts: 472
Joined: Thu Mar 26, 2020 8:52 pm

Re: Blender-like grid

Post by mnesarco »

Chris_G wrote: Thu May 11, 2017 4:19 pm @Mario,
I'll put this on the TODO list.

I have experimented with auto-scale ( grid sub-divisions that gradually appear / disappear when zooming in / out ).
Test code below ( with Pablogil's color ;) )
Currently, it only works when zooming in around origin. The grid disappears if zooming on a point away from origin.
But that's a step forward ...

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

Hi Chris_G, I have tried this with FreeCAD 0.19 (git 21125) and i get this error:

Code: Select all

Traceback (most recent call last):
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 316, in attach
    self.xy.vector1dir = (1,0,0)
  File "/tmp/.mount_FreeCAymu2jP/usr/lib/python3.8/site-packages/pivy/coin.py", line 4000, in __setattr__
    return SoBase.__setattr__(self, name, value)
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 130, in vector1dir
    self.buildGrid()
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 263, in buildGrid
    for i in range(l/2):
<class 'TypeError'>: 'float' object cannot be interpreted as an integer
Traceback (most recent call last):
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 392, in onChanged
    self.updateCam()
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 388, in updateCam
    self.xz.linkTo(self.cam)
<class 'AttributeError'>: 'gridVP' object has no attribute 'xz'
Traceback (most recent call last):
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 392, in onChanged
    self.updateCam()
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 388, in updateCam
    self.xz.linkTo(self.cam)
<class 'AttributeError'>: 'gridVP' object has no attribute 'xz'
Traceback (most recent call last):
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 392, in onChanged
    self.updateCam()
  File "/home/mnesarco/.FreeCAD/Macro/Grid.FCMacro", line 388, in updateCam
    self.xz.linkTo(self.cam)
<class 'AttributeError'>: 'gridVP' object has no attribute 'xz'
I see this thread is old, is there a new version somewhere else that works with 0.19?

Cheers.
Post Reply