Plugin Cafe Homepage
Forum Home Forum Home > Plugin Cafe > PYTHON Development
  New Posts New Posts
  FAQ FAQ  Forum Search

Monkey Voxelizer

Author
Message
  Topic Search Topic Search
monkeytack View Drop Down
Member
Member


Joined: 2014 Apr 14
Location: Germany
Online Status: Offline
Posts: 283
Direct Link To This Post Topic: Monkey Voxelizer
    Posted: 2014 May 13 at 1:24am
Hi all,

I created a voxelizer with the old school thinking particles modul, that uses the ray collider as a polygon detector in Zaxis.
Just wanted to share it.
And thanks for helping me to learn all the stuff!

One question is left: is it possible to read(and only read) the Max Particles value, to inform the user that
his voxels may exceed the given limit? -script line 240-


######################################################################################
# Copyright (c) 2014 Martin Albertshauser                                            #
#                                                                                    #
# Permission is hereby granted, free of charge, to any person obtaining a copy       #
# of this software and associated documentation files (the "Software"), to deal      #
# in the Software without restriction, including without limitation the rights       #
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell          #
# copies of the Software, and to permit persons to whom the Software is              #
# furnished to do so, subject to the following conditions:                           #
#                                                                                    #
# The above copyright notice and this permission notice shall be included in         #
# all copies or substantial portions of the Software.                                #
#                                                                                    #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR         #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,           #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE        #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER             #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,      #
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN          #
# THE SOFTWARE.                                                                      #
######################################################################################
##_________________________________monkeyvoxel______________________________________##
#__________________This script voxelizes closed meshes_______________________________#
######################################################################################
 
import c4d
import math

from c4d import utils,gui
from c4d.utils import SendModelingCommand

def axisreset(op):
    oldm= op.GetMg()
    points= op.GetAllPoints()
    pcount= op.GetPointCount()
    bbox= op.GetMp()
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
    op.SetAbsRot(c4d.Vector(0,0,0))       
    newm= op.GetMg()
    for p in xrange(pcount):
        op.SetPoint(p,~newm*oldm*points[p])
       
    op.Message(c4d.MSG_UPDATE) 
     
    oldm2= op.GetMg()
    points2= op.GetAllPoints()
    bb2= op.GetMp()
    glop= oldm*bbox
    op.SetAbsPos(c4d.Vector(glop))   
    newm2= op.GetMg()
    for p in xrange(pcount):
        op.SetPoint(p,~newm2*oldm2*points[p])
       
    op.Message(c4d.MSG_UPDATE)
    return


def voxel(xs,ys,zs):
   
    #Build a voxel object
    vox= c4d.BaseObject(c4d.Opolygon)
    vox.ResizeObject(8,6)
   
    vox.SetPoint(0,c4d.Vector(-xs/2,-ys/2,-zs/2))
    vox.SetPoint(1,c4d.Vector(-xs/2,ys/2,-zs/2))
    vox.SetPoint(2,c4d.Vector(xs/2,ys/2,-zs/2))
    vox.SetPoint(3,c4d.Vector(xs/2,-ys/2,-zs/2))
    vox.SetPoint(4,c4d.Vector(-xs/2,-ys/2,zs/2))
    vox.SetPoint(5,c4d.Vector(-xs/2,ys/2,zs/2))
    vox.SetPoint(6,c4d.Vector(xs/2,ys/2,zs/2))
    vox.SetPoint(7,c4d.Vector(xs/2,-ys/2,zs/2))
   
    vox.SetPolygon(0,c4d.CPolygon(0,1,2,3))
    vox.SetPolygon(1,c4d.CPolygon(4,5,1,0))
    vox.SetPolygon(2,c4d.CPolygon(7,6,5,4))
    vox.SetPolygon(3,c4d.CPolygon(3,2,6,7))
    vox.SetPolygon(4,c4d.CPolygon(1,5,6,2))
    vox.SetPolygon(5,c4d.CPolygon(0,4,7,3))
    vox.Message (c4d.MSG_UPDATE)
   
    return vox

def ray(op,rayposition,rayzdirection,raylength,xdim,ydim,zdim,Zcount,parSys,parGroup,parLife,pcounter):
   
    HitList= []
    PosList= []
    matr= op.GetMg()
    precision= 6
 
    #Initialize the collider
    collider= c4d.utils.GeRayCollider()
    collider.Init(op,True)
   
    #Start the ray
    inter= collider.Intersect(rayposition,rayzdirection,raylength)
    if inter== True:
        count= collider.GetIntersectionCount()
        collision= 0
       
        #Loop through all collisions
        while collision< count:
           
            hitposition= collider.GetIntersection(collision)["hitpos"]
           
            #Limit the accuracy of Zpositions in the HitList because of the rounding errors with real type calculation at BitLimit with a precision value
            hitposition.z= round(hitposition.z,precision)
       
            #Fit the voxel´s Zposition in the grid
            if hitposition.z>= 0:
                hitposition.z= (math.ceil(hitposition.z/zdim))*zdim
                if Zcount % 2== 0:
                    hitposition.z= hitposition.z-zdim/2
                else:
                    hitposition.z= hitposition.z-zdim
            else:
                hitposition.z= (math.floor(hitposition.z/zdim))*zdim
                if Zcount % 2== 0:
                    hitposition.z= hitposition.z+zdim/2
                else:
                    hitposition.z= hitposition.z+zdim
               
           
            if hitposition.z not in HitList:
                               
                backface= collider.GetIntersection(collision)["backface"]
                             
                HitList.append(hitposition.z)
                PosList.append([hitposition,backface])
               
            collision+= 1
           
           
    #Sort the PosList in Zdirection
    PosList= sorted(PosList,key= lambda x: x[0][2])
   
   
    #Place voxels on every unique collision position
    #and fill the gaps between them with voxels 
    for i,position in enumerate(PosList):
        if position[1]== False and PosList[i-1][1]!= False :
            x= position[0].x
            y= position[0].y
            z= position[0].z
           
            z1= PosList[(i-1)][0].z
           
            zdiv= ((z-z1)/zdim)-1
            i= +1       
            for g in xrange(int(zdiv)):
           
                z2= z-(g+1)*zdim
                vec2= c4d.Vector(x,y,z2)
               
               
                parSys.SetGroup(pcounter, parGroup)
                parSys.SetLife(pcounter, parLife)
                parSys.SetPosition(pcounter, (vec2*matr))
               
                pcounter+= 1
        parSys.SetGroup(pcounter, parGroup)
        parSys.SetLife(pcounter, parLife)
        parSys.SetPosition(pcounter, (position[0]*matr))        
        pcounter+= 1          
    return pcounter
   

def main():
    #Timer start
    t= c4d.GeGetTimer()
   
    #Make sure there is an active object
    if op== None: return None
   
    #Start an undo sequence
    doc.StartUndo()
   
    #Instantiate the object and reset the axis to boundingbox center:
    #Ray collider works in local space, therefore axis reset is needed
    testop= SendModelingCommand(command= c4d.MCOMMAND_CURRENTSTATETOOBJECT, list= [op.GetClone()], doc= doc)
    if not testop : return
    obj= testop[0]
    axisreset(obj)
   
    #Set voxel gaps:
    xgap= 1
    ygap= 1
    zgap= 1
   
    #Set voxel dimension:
    xdim= 30
    ydim= 30
    zdim= 30
   
    #Calculate the voxelgrid:
    #If a mulitple of voxeldimension fits exactly into boundigboxsize, take it.
    #Otherwise round up the count with a ceiling function, that the voxels covers the mesh
    boundingbox= obj.GetRad()*2

    CheckXcount= boundingbox.x/xdim
    CeilXcount= math.ceil(CheckXcount)
    if c4d.utils.CompareFloatTolerant(CheckXcount+1.0, CeilXcount)== True:
        Xcount= CheckXcount
    else:
        Xcount= CeilXcount
   
    CheckYcount= boundingbox.y/ydim
    CeilYcount= math.ceil(CheckYcount)
    if c4d.utils.CompareFloatTolerant(CheckYcount+1.0, CeilYcount)== True:
        Ycount= CheckYcount
    else:
        Ycount= CeilYcount
   
    CheckZcount= boundingbox.z/zdim
    CeilZcount= math.ceil(CheckZcount)
    if c4d.utils.CompareFloatTolerant(CheckZcount+1.0, CeilZcount)== True:
        Zcount= CheckZcount
    else:
        Zcount= CeilZcount
   
    #Calculate the origin of the VoxelGrid
    Xorg= (Xcount*xdim)/2-xdim/2
    Yorg= (Ycount*ydim)/2-ydim/2
    Zorg= (Zcount*zdim)/2
   
   
   
    #Basesettings of particles
    if doc.GetParticleSystem()== None:
        dialog=gui.MessageDialog("could not find 'Thinking Particles' module",c4d.GEMB_OK)
        if dialog== 1:
            return
    else:
        parSys= doc.GetParticleSystem()
       
    parSys.FreeAllParticles()
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, parSys)
    parMax= int((Xcount+1)*(Ycount+1)*(Zcount+1))
   
    #Warn the user that the particles may be out of range
    #don´t know how to access this Value:______________[IDC_TP_NUMLIMIT]____________________________________________
    if parMax>100000:
        dialog=gui.MessageDialog("Particle count exceeded! \nPlease raise the 'Max. Partikel' value.",c4d.GEMB_OK)
       
    parGroup= parSys.AllocParticleGroup()
    parGroup.SetTitle(op.GetName())
    parGroup[c4d.PGROUP_NAME]= (str(op.GetName())+"_Voxels")
    parGroup[c4d.PGROUP_VIEWTYPE]= 0
    parGroupGlob= parSys.GetRootGroup()
    parSys.SetPGroupHierarchy(parGroupGlob, parGroup, c4d.TP_INSERT_UNDERFIRST)
    parLife= doc.GetMaxTime()
    parSys.AllocParticles(parMax)



    #Set the direction and length of the ray that the whole boundingbox is hit by the ray
    rayzdirection= c4d.Vector(0,0,-1)
    raylength= boundingbox.z+zdim
    
    #Send rays
    pcounter= 0
    for py in xrange(int(Ycount)):
       
        y= float(ydim*py)
        for px in xrange(int(Xcount)):
           
            x= float(xdim*px)   
            Zgrid= c4d.Vector((Xorg-x),(Yorg-y),(Zorg+zdim/2))
            #recursivly raising the counter and call the rayfunction
            pcounter= ray(obj,Zgrid,rayzdirection,raylength,xdim,ydim,zdim,Zcount,parSys,parGroup,parLife,pcounter)
   
   
       
    #Objects,Tag: set and insert
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
    op.SetEditorMode(c4d.MODE_OFF)
    op.SetRenderMode(c4d.MODE_OFF)   
   
    cloner= c4d.BaseObject(1018544)
    cloner[c4d.ID_MG_MOTIONGENERATOR_MODE]= 0
    cloner[c4d.MG_OBJECT_LINK]= parGroup
    cloner[c4d.MGCLONER_FIX_CLONES]= False
    cloner[c4d.MG_OBJECT_ALIGN]= True
    cloner.SetName(str(op.GetName())+"_Voxels")
    doc.InsertObject(cloner)
   
    display= cloner.MakeTag(c4d.Tdisplay)
    display[c4d.DISPLAYTAG_AFFECT_DISPLAYMODE]= True
    display[c4d.DISPLAYTAG_SDISPLAYMODE]= 4
    display[c4d.DISPLAYTAG_WDISPLAYMODE]= 2
    doc.AddUndo(c4d.UNDOTYPE_NEW,cloner)
   
    vox= voxel(xdim-xgap,ydim-ygap,zdim-zgap)
    vox.SetName("Voxel")
    vox.InsertUnder(cloner)
    doc.AddUndo(c4d.UNDOTYPE_NEW,vox) 
     
    random= c4d.BaseObject(1018643)
    random.SetName("Voxel_Color")
    random.InsertBefore(cloner)
    doc.AddUndo(c4d.UNDOTYPE_NEW,random)
   
    random[c4d.ID_MG_BASEEFFECTOR_COLOR_MODE]= 3
    random[c4d.ID_MG_BASEEFFECTOR_POSITION_ACTIVE]= False
   
    inexcludeList= cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]
    inexcludeList.InsertObject(random, 1)
    cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]= inexcludeList
   
    doc.EndUndo()
    c4d.EventAdd(c4d.EVENT_FORCEREDRAW)
   
   
    time1= c4d.GeGetTimer() - t
    print str(parSys.GetGroupParticleCount(parGroup, subgroups= False))+" build in "+ str(time1) + "msec"
   
   
if __name__=='__main__':
    main()







Edited by monkeytack - 2014 May 13 at 3:01am
Back to Top
pgrooff View Drop Down
Member
Member


Joined: 2010 Nov 05
Online Status: Offline
Posts: 817
Direct Link To This Post Posted: 2014 May 13 at 7:15am
Incredible speed. 
Thanks!
Back to Top
monkeytack View Drop Down
Member
Member


Joined: 2014 Apr 14
Location: Germany
Online Status: Offline
Posts: 283
Direct Link To This Post Posted: 2014 May 14 at 1:42am
I´m glad you tested it, Pim !
1000000 voxels in 2531msec was my fastest approach, great so far!
I jumped into programming a few months ago and this is my first a little bit more complex script, therefore if anyone has some criticism to pass or some improvements, I would be thankful.
Cheers
Martin
Back to Top
pgrooff View Drop Down
Member
Member


Joined: 2010 Nov 05
Online Status: Offline
Posts: 817
Direct Link To This Post Posted: 2014 May 14 at 6:29am
It is looking very good. 
So nothing major, just some observations:
- give out a message when no object is selected
- why not just create a Cube instead of using your voxel function?
- the particle group has the same name as the cloner. For me that was a bit confusing.
Also you seem to set the particle group name twice:
    parGroup.SetTitle(op.GetName())
    parGroup[c4d.PGROUP_NAME]= (str(op.GetName())+"_Voxels")

To see the difference in the 2 approaches (algorithms) I took a standard cinema 4d figure object, transformed it to polygons and used the 2 ways.
- using raytracing it took about 4 mseconds and 336 voxels
- using "polygon vertex in voxel" it took about 4 seconds and 1013 voxels (displayed timing seems wrong)
Note: the number of voxels depends on the given max surface. Here 200.


Back to Top
monkeytack View Drop Down
Member
Member


Joined: 2014 Apr 14
Location: Germany
Online Status: Offline
Posts: 283
Direct Link To This Post Posted: 2014 May 14 at 12:40pm

Thanks for your comments!
- dialog for  if op== None , useful idea.
- I intended to write a remesher with this, therefore a self built cube object was   
  needed and if the user wanted to convert the cloner it would be one click less.
  On the other hand a fillet option would be nice, too.
- And yes, it should be called Voxel_Group.

I´m really curious finding out which method will be faster and more reliable at the end of the day;
the ray or the brute force method.
Especially if it comes to a denser voxelgrid or a denser mesh.
Is it possible for you to test it with a dimension of maybe 5 units?
Or  - if you are interested - I would write you an E-mail and we could compare the scripts during the development.

Another improvement of the ray method will be setting the x axis to the shortest boundingbox expansion to minimize the grid resolution. I´ve missed to implement it to the axis reset function.
This is most obvious with the c4d figure object.

It seems that your voxelgrid is not centered right, cause the figure object is symmetrical but your voxelobject isn't.

Cheers!
Martin
Back to Top
pgrooff View Drop Down
Member
Member


Joined: 2010 Nov 05
Online Status: Offline
Posts: 817
Direct Link To This Post Posted: 2014 May 15 at 12:00am
I already saw that increasing the nr of voxels or polygons has a dramatic impact on the brute force and less on the ray method.
I will send you an email and start some developing together.
-Pim
Back to Top
monkeytack View Drop Down
Member
Member


Joined: 2014 Apr 14
Location: Germany
Online Status: Offline
Posts: 283
Direct Link To This Post Posted: 2014 May 15 at 3:36am
The ray method has also some deficiencies right now.
But let´s figure this out and develop it together.
I´ll mail you.
Cheers
Martin
Back to Top

Forum Jump Forum Permissions View Drop Down

Bulletin Board Software by Web Wiz Forums® version 9.61 [Free Express Edition]
Copyright ©2001-2009 Web Wiz

This page was generated in 0.109 seconds.