Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Recent
    • Tags
    • Users
    • Login

    Monkey Voxelizer

    Scheduled Pinned Locked Moved PYTHON Development
    7 Posts 0 Posters 752 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • H Offline
      Helper
      last edited by

      On 13/05/2014 at 01:24, xxxxxxxx wrote:

      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()  
        
      
      1 Reply Last reply Reply Quote 0
      • H Offline
        Helper
        last edited by

        On 13/05/2014 at 07:15, xxxxxxxx wrote:

        Incredible speed. 
        Thanks!

        1 Reply Last reply Reply Quote 0
        • H Offline
          Helper
          last edited by

          On 14/05/2014 at 01:42, xxxxxxxx wrote:

          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

          1 Reply Last reply Reply Quote 0
          • H Offline
            Helper
            last edited by

            On 14/05/2014 at 06:29, xxxxxxxx wrote:

            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.

            1 Reply Last reply Reply Quote 0
            • H Offline
              Helper
              last edited by

              On 14/05/2014 at 12:40, xxxxxxxx wrote:

              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

              1 Reply Last reply Reply Quote 0
              • H Offline
                Helper
                last edited by

                On 15/05/2014 at 00:00, xxxxxxxx wrote:

                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

                1 Reply Last reply Reply Quote 0
                • H Offline
                  Helper
                  last edited by

                  On 15/05/2014 at 03:36, xxxxxxxx wrote:

                  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

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post