Current version of the code
#!/usr/bin/python

# Path to GEOUNED Package


# only if modules are not in the PYTHONPATH or directly installed in the python distribution used
import sys
#geo_path="C:\\Users\\Juan\\Documents\\work\\GEOUNED\\RepoGit\\GitHub\\GEOUNEDcode\\src"
#sys.path.append(geo_path)
#sys.path.append('C:\\Program Files\\FreeCAD 0.19\\bin...')

import GEOUNED 
from  GEOReverse import reverse

runReverse = False
if len(sys.argv) < 2 :
   inifile = 'config.ini'

elif len(sys.argv) == 2 :
   if sys.argv[1] == '-r':
      runReverse = True
      inifile = 'configReverse.ini'
   else:
      inifile = sys.argv[1]

elif len(sys.argv) == 3:
   if   sys.argv[1] == '-r':
      runReverse = True
      inifile = sys.argv[2]
   elif sys.argv[2] == '-r':
      runReverse = True
      inifile = sys.argv[1]
   else:
      print('Bad option')
      exit()
else:
   print('Too many input arguments')
   exit()


if not runReverse :
  GEO = GEOUNED.GEOUNED(inifile)
  GEO.SetOptions()
  GEO.Start()

else:
  print(inifile)
  reverse(inifile)

scripts/geouned.py
GEOUNED-code/GEOUNED
''' Starting beta stable version 0.9''' 

GEOReverse_Version = '0.9'
GEOReverse_ReleaseDate = '04/10/2023'


src/GEOReverse/CodeVersion.py
GEOUNED-code/GEOUNED
import os
import sys
import re
import FreeCAD
from GEOReverse.Modules.Parser import parser  as mp
from GEOReverse.Modules.remh import cell_card_string, remove_hash
from GEOReverse.Modules.Objects import *
import math
import numpy as np
from numpy import linalg as LA

class MCNPinput:
    def __init__(self,name):
        if not os.path.isfile(name):
            print("File %s does not exist" %name)
            sys.exit()
            return
        self.__inputcards__  = list(mp.get_cards(name))
        self.Transformations = self.__getTransList__()
        return

    def GetFilteredCells(self,Surfaces,config):
        levels,contLevels,Universes = self.GetLevelStructure()

        FilteredCells = {} 
        
        Ustart = config['Ustart'] 
        subUniverses = getSubUniverses(Ustart,Universes)
        subUniverses.add(Ustart)

        if config['levelMax'] =='all':
           levelMax = len(levels)
        else :   
           levelMax = config['levelMax'] + 1             

        levelUniverse = set() 
        for lev in range(0,levelMax) :             
           for U in levels[lev]:
              levelUniverse.add(U)
        subUniverses = subUniverses.intersection(levelUniverse)

        for U in list(Universes.keys()):
            if U not in subUniverses : del(Universes[U])
              
        for U in Universes.keys():
            FilteredCells[U] = selectCells(Universes[U],config)
            processSurfaces(FilteredCells[U],Surfaces)

        # change the surface name in surface dict
        newSurfaces = {}
        for k in Surfaces.keys():
           newkey = Surfaces[k].id
           newSurfaces[newkey] = Surfaces[k]

        
        for U,universe in FilteredCells.items() :
            substituteLikeCell(universe,newSurfaces)
            
            # set cell as CAD cell Object
            for cname,c in universe.items():
               #print(cname,c.geom.str)
               universe[cname] = CADCell(c)
           
        return levels,FilteredCells,newSurfaces     



    def GetLevelStructure(self):      
        containers = []
        Universe_dict = {}
        cellCards = {}

        
                
        for c in self.__inputcards__ :
          if c.ctype != mp.CID.cell : continue
          c.get_values()
          cstr = cell_card_string(''.join(c.lines))

          if cstr.TRCL : cstr.TRCL = TransformationMatrix(cstr.TRCL,self.Transformations)
          if cstr.TR   : cstr.TR   = TransformationMatrix(cstr.TR,self.Transformations)
          cellCards[c.name] = cstr
 
        setExplicitCellDefinition(cellCards)  
        for cname,c in cellCards.items():  
          if c.U is None: c.U = 0
          if c.U not in Universe_dict.keys(): Universe_dict[c.U] = {}
          Universe_dict[c.U].update({c.name:c})

          if c.FILL :
             containers.append(c)
             
        
        currentLevel = [0]
        nextLevel    = []
        contLevel    ={0:[(0,0)]}
        univLevel    = {0 : {0}}
        level = 0
        
        while True:
           level += 1 
           contLevel[level] = []
           univLevel[level] = set()
           for c in reversed(containers) :
              if c.U in currentLevel :
                 c.Level = level
                 nextLevel.append(c.FILL)
                 contLevel[level].append((c.U,c.name))
                 univLevel[level].add(c.FILL)
                 
                 containers.remove(c)

           if nextLevel == [] : break
           currentLevel = nextLevel
           nextLevel = []
 
        return univLevel,contLevel,Universe_dict   

    
    def GetCells(self,U=None,Fill=None):
        cell_cards = {}
        for c in self.__inputcards__ :
          if c.ctype != mp.CID.cell : continue
          c.get_values()
          U_cell = c.get_u()
          Fill_cell = c.get_f()
          if ( U is None and Fill is None) :
            cell_cards[c.name] = c
          elif U_cell == U and U is not None:
            cell_cards[c.name] = c  
          elif Fill_cell == Fill and Fill is not None:
            cell_cards[c.name] = c  

        return cell_cards  
        
    def GetSurfaces(self,scale=1.):
        surf_cards = {}
        number = 1
        for c in self.__inputcards__ :
          if c.ctype != mp.CID.surface : continue
          c.get_values()
          c.TR = TransformationMatrix(c.TR,self.Transformations)
          surf_cards[c.name] = (c.stype,c.scoefs,c.TR,number)
          number += 1

        # return surface as surface Objects type
        return Get_primitive_surfaces(surf_cards,scale)

    def __getTransList__(self):
        trl = {}
        for c in self.__inputcards__ :
          if c.ctype != mp.CID.data : continue
          c.get_values()
          if c.dtype == 'TRn' :
             trValues = []
             for v in c.values[1:]:
                trValues.append(v[0])
             trl[c.name]= getTransMatrix(trValues,c.unit)     
        return trl     


def getTransMatrix(trsf,unit='',scale=10.):
    
    if len(trsf) == 3 :
       trsfMat = FreeCAD.Matrix(1,0,0, trsf[0]*scale,
                                0,1,0, trsf[1]*scale,
                                0,0,1, trsf[2]*scale,
                                0,0,0, 1)
    else:
       if unit == '*':
          coeff = tuple(map(math.radians,trsf[3:12] )) 
          coeff = tuple(map(math.cos,coeff))
       else:
          coeff = trsf[3:12] 

       trsfMat = FreeCAD.Matrix(coeff[0],coeff[3] ,coeff[6], trsf[0]*scale,
                                coeff[1],coeff[4] ,coeff[7], trsf[1]*scale,
                                coeff[2],coeff[5] ,coeff[8], trsf[2]*scale,
                                0,0,0, 1)
    
def substituteLikeCell(universe,Surfaces):
    number = re.compile(r"\#?\s*\d+")
    newId = len(Surfaces)

    # create new cells objects for like cell cells    
    for c in universe.values(): 
       if c.likeCell : c.geom = universe[c.likeCell].geom.copy()
       if not c.TRCL : continue   # likebut cell should have TRCL card

    # transform change cell the parameters if needed
    for c in universe.values():
       if not c.TRCL : continue 
       cellSurf = c.geom.getSurfacesNumbers()
       surfDict = {}
       for surf in cellSurf:
           newId += 1
           surfDict[surf] = newId
           Surfaces[newId] = Surfaces[surf].copy()
           Surfaces[newId].id = newId
           Surfaces[newId].transform(c.TRCL)
       if c.FILL : c.TR = c.TRCL
       c.TRCL = None
           
       # rename the surface of the like cell
       pos = 0
       while True:
         m = number.search(c.geom.str,pos)
         if not m : break
         if "#" in m.group():
               pos = m.end()
               continue
         surf = int(m.group())
         pos = c.geom.replace(surf,surfDict[surf],pos)
         if pos < 0 : break            
       
def selectCells(cellList,config):
    selected = {}
    # options are 'all' material                     
    if config['mat'][0] == 'all' : 
       if config['cell'][0]=='all' :
          selected = cellList
       elif config['cell'][0]=='exclude' :
          for name,c in cellList.items(): 
             if name not in config['cell'][1]:
                selected[name] = c
       elif config['cell'][0]=='include' :
          for name,c in cellList.items(): 
             if name in config['cell'][1]:
                selected[name] = c

     # options are 'exclude' material
    elif config['mat'][0] == 'exclude':            
       if config['cell'][0]=='all' :
          for name,c in cellList.items():
             if c.FILL is None:
                 if c.MAT not in config['mat'][1]: 
                    selected[name] = c
             else:       
                 selected[name] = c # Fill cell are not tested against material number
       elif config['cell'][0]=='exclude' :
          for name,c in cellList.items():
             if c.FILL is None:
                 if c.MAT not in config['mat'][1]:
                    if name not in config['cell'][1]: 
                      selected[name] = c
             else:
                 if name not in config['cell'][1]:
                    selected[name] = c # Fill cell are not tested against material number
       elif config['cell'][0]=='include' :
          for name,c in cellList.items():
             if c.FILL is None:
                 if c.MAT not in config['mat'][1]:
                    if name in config['cell'][1]: 
                      selected[name] = c
             else:
                 if name in config['cell'][1]:
                    selected[name] = c # Fill cell are not tested against material number

     # options are 'include' material
    elif config['mat'][0] == 'include':            
       if config['cell'][0]=='all' :
          for name,c in cellList.items():
             if c.FILL is None:
                 if c.MAT in config['mat'][1]: 
                    selected[name] = c
             else:       
                 selected[name] = c # Fill cell are not tested against material number
       elif config['cell'][0]=='exclude' :
          for c in cellList:
             if c.FILL is None:
                 if c.MAT in config['mat'][1]:
                    if name not in config['cell'][1]: 
                      selected[name] = c
             else:
                 if name not in config['cell'][1]:
                    selected[name] = c # Fill cell are not tested against material number
       elif config['cell'][0]=='include' :
          for name,c in cellList.items():
             if c.FILL is None:
                 if c.MAT in config['mat'][1]:
                    if name in config['cell'][1]: 
                      selected[name] = c
             else:
                 if name in config['cell'][1]:
                    selected[name] = c # Fill cell are not tested against material number


    # remove complementary in cell of the universe
    for cname,c in selected.items() :
       c.geom = remove_hash(cellList,cname)

    return selected    
        
#Change implicit cell definition (like-but type or cell with #)
# to explicit cell definition (only surface number)
def setExplicitCellDefinition(universeCells):
    for cname,c in universeCells.items():
        if c.likeCell :
           lkc = universeCells[c.likeCell]
           c.geom = lkc.geom
           if not c.U     : c.U    = lkc.U
           if not c.FILL  : c.FILL = lkc.FILL
           if not c.MAT   : c.MAT  = lkc.MAT
           if not c.TR    : c.TR   = lkc.TR
           if not c.TRCL  : c.TRCL = lkc.TRCL
    return         


def processSurfaces(UCells,Surfaces):          
    number = re.compile(r"\#?\s*\d+")

    for cname,c in UCells.items() :
        c.geom.remove_comments(full=True) 
        pos = 0
        while True:
           m = number.search(c.geom.str,pos)
           if not m : break
           if "#" in m.group():
               pos = m.end()
               continue
           surf = int(m.group())
           if surf == 0 :
              print(c.name)
              print(m)
              print(c.geom.str)
           pos = c.geom.replace(surf,Surfaces[surf].id,pos)


def getTransMatrix(trsf,unit='',scale=10.):

    if len(trsf) == 3 :
       trsfMat = FreeCAD.Matrix(1,0,0, trsf[0]*scale,
                                0,1,0, trsf[1]*scale,
                                0,0,1, trsf[2]*scale,
                                0,0,0, 1)
    else:
       if unit == '*':
          coeff = tuple(map(math.radians,trsf[3:12] )) 
          coeff = tuple(map(math.cos,coeff))
       else:
          coeff = trsf[3:12] 

       trsfMat = FreeCAD.Matrix(coeff[0],coeff[3] ,coeff[6], trsf[0]*scale,
                                coeff[1],coeff[4] ,coeff[7], trsf[1]*scale,
                                coeff[2],coeff[5] ,coeff[8], trsf[2]*scale,
                                0,0,0, 1)
    return trsfMat   
       
def TransformationMatrix(TRSF,Transformations):

    if TRSF :
       if type(TRSF) is int :
          return Transformations[TRSF]

       else:
          return getTransMatrix(TRSF) 
    else:      
       return TRSF      



def getSubUniverses(Ustart,Universes):
    Uid = set() 
    for c in Universes[Ustart].values() :
        if c.FILL : Uid.add(c.FILL)

    AllU = Uid.copy()
    for U in Uid:
        AllU = AllU.union(getSubUniverses(U,Universes))
        
    return AllU    

# traduce mcnp surface definition for Solid_Cell class
#  planes:
#     Stype = 'plane'
#     params = [ax,ay,az,d]
#
#  spheres:
#     Stype = 'shpere'
#     params = [cx,cy,cz,R]
#
#  cylinders:
#     Stype = 'cylinder'
#     params = [[px,py,pz],[vx,vy,vz],R]
#
#  cones:
#     Stype = 'cone'
#     params = [[px,py,pz],[vx,vy,vz],t,sht]
#
#  torus:
#     Stype = 'torus'
#     params = [[px,py,pz],[vx,vy,vz],ra,r]

# Return a diccionary with the corresponding surface Object
def Get_primitive_surfaces(mcnp_surfaces,scale=10.) :

      X_vec = FreeCAD.Vector(1.0,0.0,0.0)
      Y_vec = FreeCAD.Vector(0.0,1.0,0.0)
      Z_vec = FreeCAD.Vector(0.0,0.0,1.0)
      negX_vec = -X_vec
      negY_vec = -Y_vec
      negZ_vec = -Z_vec
      origin= FreeCAD.Vector(0.0,0.0,0.0)

      surfaces = {}
      for Sid in mcnp_surfaces.keys() :      
         MCNPtype   = mcnp_surfaces[Sid][0].upper()
         MCNPparams = mcnp_surfaces[Sid][1]
         trsf       = mcnp_surfaces[Sid][2]   
         number     = mcnp_surfaces[Sid][3]
         
         params = []
         Stype = None
         if MCNPtype in ('P','PX','PY','PZ'):
            Stype = 'plane'
            if MCNPtype == 'P':
                normal = FreeCAD.Vector(MCNPparams[0:3])
                params = (normal, MCNPparams[3]*scale)
            elif MCNPtype == 'PX':
                params = (X_vec, MCNPparams[0]*scale )
            elif MCNPtype == 'PY':
                params = ( Y_vec, MCNPparams[0]*scale )
            elif MCNPtype == 'PZ':
                params = ( Z_vec, MCNPparams[0]*scale )

                
         elif MCNPtype in ['S','SX','SY','SZ','SO'] :
            Stype = 'sphere'
            if MCNPtype == 'S':
                params = (FreeCAD.Vector(MCNPparams[0:3])*scale, MCNPparams[3]*scale)
            elif MCNPtype == 'SX':
                params = ( FreeCAD.Vector(MCNPparams[0]*scale, 0.0, 0.0), MCNPparams[1]*scale )
            elif MCNPtype == 'SY':
                params = ( FreeCAD.Vector(0.0, MCNPparams[0]*scale, 0.0), MCNPparams[1]*scale )
            elif MCNPtype == 'SZ':
                params = ( FreeCAD.Vector(0.0, 0.0, MCNPparams[0]*scale), MCNPparams[1]*scale )
            elif MCNPtype == 'SO':
                params = ( origin, MCNPparams[0]*scale )

         elif MCNPtype in ['CX','C/X','CY','C/Y','CZ','C/Z'] :
            if MCNPtype in ['CX','CY','CZ']:
               R = MCNPparams[0]
            else:
               R = MCNPparams[2]
               x1 = MCNPparams[0]
               x2 = MCNPparams[1]
               
            Stype = 'cylinder'
        
            if MCNPtype == 'CX':
                v = X_vec
                p = origin
            elif MCNPtype == 'CY':
                v = Y_vec
                p = origin
            elif MCNPtype == 'CZ':
                v = Z_vec
                p = origin
            elif MCNPtype ==  'C/X':
                v = X_vec
                p = FreeCAD.Vector(0., x1, x2)
            elif MCNPtype == 'C/Y':
                v = Y_vec
                p = FreeCAD.Vector(x1, 0., x2)
            elif MCNPtype == 'C/Z':
                v = Z_vec
                p = FreeCAD.Vector(x1, x2, 0.)

            if scale != 1.0 :
              p = p.multiply(scale)
              R *= scale

            params = ( p,v,R )  

         elif MCNPtype in ['KX','KY','KZ'] :
            Stype = 'cone' 
            x1 = MCNPparams[0]
            t2 = MCNPparams[1]
            t  = math.sqrt(t2)
            dblsht = True

            if len(MCNPparams) == 3:
              dblsht = False
              sht = MCNPparams[2]  
              if sht == 0. :
                dblsht = True

            if MCNPtype == 'KX':
                p  = FreeCAD.Vector(x1,0.0,0.0)
                v  = X_vec
                if not dblsht :
                  if sht < 0 :
                    v = negX_vec
            elif MCNPtype == 'KY':
                p  = FreeCAD.Vector(0.0,x1,0.0)
                v  = Y_vec
                if not dblsht :
                  if sht < 0 :
                     v = negY_vec
            elif MCNPtype == 'KZ':
                p  = FreeCAD.Vector(0.0,0.0,x1)
                v  = Z_vec
                if not dblsht :
                  if sht < 0 :
                     v = negZ_vec             

            p = p.multiply(scale)
            params = (p,v,t,dblsht)

                         
         elif MCNPtype in ['K/X','K/Y','K/Z'] :
            Stype = 'cone' 
            x1 = MCNPparams[0]
            x2 = MCNPparams[1]
            x3 = MCNPparams[2]
            p  = FreeCAD.Vector(x1,x2,x3)
            t2 = MCNPparams[3]
            t  = math.sqrt(t2)
            dblsht = True

            if len(MCNPparams) == 5:
              dblsht = False
              sht = MCNPparams[4]  
              if sht == 0. :
                dblsht = True

            if MCNPtype == 'K/X':
              v  = X_vec
              if not dblsht :
                 if sht < 0 :
                    v = negX_vec
            elif MCNPtype == 'K/Y':
                v  = Y_vec
                if not dblsht :
                   if sht < 0 :
                      v = negY_vec
            elif MCNPtype == 'K/Z':
                v  = Z_vec
                if not dblsht :
                   if sht < 0 :
                      v = negZ_vec             

            p = p.multiply(scale)
            params = (p,v,t,dblsht)

         elif MCNPtype in ['TX','TY','TZ'] :
            Stype = 'torus'
            x1 = MCNPparams[0]
            x2 = MCNPparams[1]
            x3 = MCNPparams[2]
            p  = FreeCAD.Vector(x1,x2,x3)
            Ra = MCNPparams[3]
            r1 = MCNPparams[4]
            r2 = MCNPparams[5]
            if (r1 != r2 ) :
               print('ellipsoid torus not implemented : {} {}'.format(r1,r2) )
            R = (r1+r2)*0.5         

            if   MCNPtype == 'TX':
              v  = X_vec
            elif MCNPtype == 'TY':
              v  = Y_vec
            elif MCNPtype == 'TZ':
              v  = Z_vec
                
            if scale != 1.0 :
              Ra *= scale
              R  *= scale
              p = p.multiply(scale)

            params = ( p,v,Ra,R)  

         elif MCNPtype == 'GQ' :
            Qparams = tuple(MCNPparams[0:10])   
            Stype,quadric = gq2cyl(Qparams)

            if Stype == 'cylinder' :
                p = FreeCAD.Vector(quadric[0:3])
                v = FreeCAD.Vector(quadric[3:6])
                R = quadric[6]
                if scale != 1.0 :
                  R *= scale
                  p = p.multiply(scale)

                params = ( p,v,R )
                
            elif Stype == 'cone' :
                p = FreeCAD.Vector(quadric[0:3])
                v = FreeCAD.Vector(quadric[3:6])
                t = quadric[6]
                dblsht = quadric[7]
                if scale != 1.0 :
                    p = p.multiply(scale)
                 
                params = ( p,v,t,dblsht )

            else:
                print ( Stype )
                params = None
#                get_quadric_surface(params)

         elif MCNPtype == 'X' :
             if len(MCNPparams) == 2 :
                Stype = 'plane'
                params = (X_vec, MCNPparams[0]*scale )
             elif len(MCNPparams) == 4 :
                 if ( abs(MCNPparams[1] - MCNPparams[3]) ) > 1.e-12 :
                    Stype = 'cone'
                    dblsht = False
                    t = (MCNPparams[3] - MCNPparams[1])/(MCNPparams[2] - MCNPparams[0])
                    x =  MCNPparams[0] - MCNPparams[1]/t 
                    if (MCNPparams[0]-x)*(MCNPparams[2]-x) > 0 :
                        p = FreeCAD.Vector(x,0.,0.)
                        if (MCNPparams[0]-x) > 0 :
                           v = X_vec
                        else:
                           v = negX_vec 
                        if scale != 1.0 : p *= scale
                        params = (p,v,abs(t),dblsht)
                 elif  abs(MCNPparams[1]) < 1.e-12 :
                    Stype = 'plane' 
                    if ( abs(MCNPparams[0] - MCNPparams[2]) ) < 1.e-12 :
                        params = (X_vec, MCNPparams[0]*scale )
                 else:    
                    Stype = 'cylinder'
                    if scale != 1.0 :
                       p = p.multiply(scale)
                       R *= scale
                    params = ( origin,X_vec,MCNPparams[1] )
             else:
                 print('not implemented surfaces defined by point with more than 2couples of value')  

         elif MCNPtype == 'Y' :
             if len(MCNPparams) == 2 :
                Stype = 'plane'
                params = (Y_vec, MCNPparams[0]*scale )
             elif len(MCNPparams) == 4 :
                 if ( abs(MCNPparams[1] - MCNPparams[3]) ) > 1.e-12 :
                    Stype = 'cone'
                    dblsht = False
                    t = (MCNPparams[3] - MCNPparams[1])/(MCNPparams[2] - MCNPparams[0])
                    y = MCNPparams[0] - MCNPparams[1]/t
                    if (MCNPparams[0]-y)*(MCNPparams[2]-y) > 0 :
                        p = FreeCAD.Vector(0.,y,0.)
                        if (MCNPparams[0]-y) > 0 :
                           v = Y_vec
                        else:
                           v = negY_vec 
                        if scale != 1.0 : p = p.multiply(scale)
                        params = (p,v,abs(t),dblsht)
                 elif  abs(MCNPparams[1]) < 1.e-12 :
                    Stype = 'plane' 
                    if ( abs(MCNPparams[0] - MCNPparams[2]) ) < 1.e-12 :
                        params = (Y_vec, MCNPparams[0]*scale )
                 else:    
                    Stype = 'cylinder'
                    if scale != 1.0 :
                       p = p.multiply(scale)
                       R *= scale
                    params = ( origin,Y_vec,MCNPparams[1] )
             else:
                 print('not implemented surfaces defined by point with more than 2couples of value')
                 
         elif MCNPtype == 'Z' :
             if len(MCNPparams) == 2 :
                Stype = 'plane'
                params = (Z_vec, MCNPparams[0]*scale )
             elif len(MCNPparams) == 4 :
                 if ( abs(MCNPparams[1] - MCNPparams[3]) ) > 1.e-12 :
                    Stype = 'cone'
                    dblsht = False
                    t = (MCNPparams[3] - MCNPparams[1])/(MCNPparams[2] - MCNPparams[0])
                    z = MCNPparams[0] - MCNPparams[1]/t
                    if (MCNPparams[0]-z)*(MCNPparams[2]-z) > 0 :
                        p = FreeCAD.Vector(0.,0.,z)
                        if (MCNPparams[0]-z) > 0 :
                           v = Z_vec
                        else:
                           v = negZ_vec 
                        if scale != 1.0 : p = p.multiply(scale)
                        params = (p,v,abs(t),dblsht)
                 elif  abs(MCNPparams[1]) < 1.e-12 :
                    Stype = 'plane' 
                    if ( abs(MCNPparams[0] - MCNPparams[2]) ) < 1.e-12 :
                        params = (Z_vec, MCNPparams[0]*scale )
                 else:    
                    Stype = 'cylinder'
                    if scale != 1.0 :
                       p = p.multiply(scale)
                       R *= scale
                    params = ( origin,Z_vec,MCNPparams[1] )
             else:
                 print('not implemented surfaces defined by point with more than 2couples of value')                 
     

         if Stype == 'plane':
             surfaces[Sid] = Plane(number, params, trsf)
         elif Stype == 'sphere':
             surfaces[Sid] = Sphere(number, params, trsf)    
         elif Stype == 'cylinder':
             surfaces[Sid] = Cylinder(number, params,trsf)
         elif Stype == 'cone':
             surfaces[Sid] = Cone(number, params,trsf)
         elif Stype == 'torus':
             surfaces[Sid] = Torus(number, params,trsf)
         else :
             print('Undefined',Sid)
             print( MCNPtype ,number,MCNPparams) 

      return surfaces 

def gq2cyl(x):
# Conversion de GQ a Cyl
# Ax2+By2+Cz2+Dxy+Eyz+Fxz+Gx+Hy+Jz+K=0
# x.T*M*x + b.T*x + K = 0
  minWTol=5.e-2
  minRTol=1.e-3
  #minRTol=3.e-1
  # lx = np.array(x)
  tp = ''
  M = np.array( [[x[0],x[3]/2,x[5]/2], \
                 [x[3]/2,x[1],x[4]/2], \
                 [x[5]/2,x[4]/2,x[2]]] )
  w,P = LA.eigh(M)
  sw = np.sort(w)
  aw = np.abs(w)
  asw= np.sort(aw)
  # Test for cylinder (least abs value is much less than others)
  if asw[0]<minWTol*asw[1]:
    tp = 'cylinder'
    rv = [0.]*7   # X0,Y0,Z0, VX, VY, VZ, R
    iaxis = np.where(aw==asw[0])[0][0]
    otherAxes = ( (iaxis+1)%3, (iaxis+2)%3 )
    if abs(w[otherAxes[0]]-w[otherAxes[1]])>minRTol*asw[2]:
       tp = 'not found - ellipsoid cylinder'
       rv = [0]
       return tp,rv
    # Vector de desplazamiento
    # x0 = -0.5*Pt*D-1*P*b pero ojo que un lambda es cero
    # P es la matriz de valores propios
    b = np.array(x[6:9])
    Pb= np.matmul(P,b)
    for i in otherAxes: Pb[i] /= w[i]
    x0 = -0.5*np.matmul(P.T,Pb)
    k  = -0.5*np.matmul(x0,b) - x[9]
    # Resultados finales

    rv[0:3] = x0                     # Punto del eje
    rv[3:6] = P[:,iaxis]             # Vector director
    rv[6]   = np.sqrt(k/sw[1])       # Radio
  # Test for cone (incomplete, returns empty data list)
  elif np.sign(sw[0])!=np.sign(sw[2]):   # maybe cone
    tp = 'cone'
    rv = [0.]*8   #  X0, Y0, Z0, VX, VY, VZ, tgAlpha, double sheet
    if np.sign(sw[0])==np.sign(sw[1]):
      iaxis = np.where(w==sw[2])[0][0]
    else:
      iaxis = np.where(w==sw[0])[0][0]
    otherAxes = ( (iaxis+1)%3, (iaxis+2)%3 )
    if abs(w[otherAxes[0]]-w[otherAxes[1]])>minRTol*asw[2]:
       tp = 'not found - ellipsoid cone/hyperboloid'
       rv = [0]
       return tp,rv
    # Displacement vector ( x0 = -0.5*M^-1*b = -0.5*P.T*D^-1*P*b
    b = np.array(x[6:9])
    x0= -0.5*np.matmul(P,np.matmul(P.T,b)/w)
    k = x0.T @ M @ x0 - x[9]  
    if np.abs(k*w[iaxis])>minRTol*minRTol*asw[2]:
       
      # tp = 'not found - hyperboloid'
      # rv = [0]
        # force cone surface
        print('Force cone surface')
        tp = 'cone'
        rv[0:3] = x0                                   # vertex point
        rv[3:6] = P[:,iaxis]                           # axis direction
        rv[6]   = np.sqrt(-w[otherAxes[0]]/w[iaxis])   # semiangle tangent
        rv[7]   = True                                 # here always double sheet cones
        return tp,rv   
    # return value
    rv[0:3] = x0                                   # vertex point
    rv[3:6] = P[:,iaxis]                           # axis direction
    rv[6]   = np.sqrt(-w[otherAxes[0]]/w[iaxis])   # semiangle tangent
    rv[7]   = True                                 # here always double sheet cones
  else:
       tp = 'not found - unknown'
       rv = [0]
  return tp,rv

src/GEOReverse/Modules/MCNPinput.py
GEOUNED-code/GEOUNED
from GEOReverse.Modules.buildSolidCell import BuildSolid
import FreeCAD, Part
import numpy as np
from GEOReverse.Modules.remh import cline
from GEOReverse.Modules.Utils.booleanFunction import outterTerms,BoolSequence
from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids

class CADCell:
    def __init__(self,stringCell=None):

        if not stringCell :
            self.surfaces = {}
            self.surfaceList = []
            self.shape    =  None
            #self.likeCell = None
            self.definition = None
            self.name     = 0
            #self.TRCL     = None  # cell transformacion "like-but" cells
            self.TRFL     = None    # Universe transformation in fill Universe
            self.U        = -1     # Cell Universe number
            self.FILL     = 0  # Fill Universe number
            self.MAT      = 0   # material number
            self.CurrentTR = None
            self.level   = None
            self.__defTerms__ = None
            self.__operator__ = None
        else:
            self.surfaces = None
            self.shape    = None
            self.name     = stringCell.name
            self.TRFL     = stringCell.TR    # Universe transformation in fill Universe
            self.U        = stringCell.U     # Cell Universe number
            self.FILL     = stringCell.FILL  # Fill Universe number
            self.MAT      = stringCell.MAT   # material number
            self.CurrentTR = self.TRFL
            self.level  = None
            
            self.__defTerms__ = None
            self.__operator__ = None        
            self.__setDefinition__(stringCell)
               
    def copy(self):
        cpCell = CADCell() 
        cpCell.surfaceList = self.surfaceList[:]
        cpCell.surfaces = {}
        for name,s in self.surfaces.items():
            cpCell.surfaces[name] = s.copy()
            
        if type(self.definition) is cline :  
           cpCell.definition = cline(self.definition.str)

        elif type(self.definition) is BoolSequence:
           cpCell.definition = self.definition.copy()
         
        cpCell.name       = self.name
        cpCell.TRFL       = self.TRFL
        cpCell.U          = self.U
        cpCell.FILL       = self.FILL
        cpCell.MAT        = self.MAT
        cpCell.level      = self.level

        if self.CurrentTR is not None :
            cpCell.CurrentTR  = self.CurrentTR.submatrix(4)

        if self.shape is not None :
          cpCell.shape      = self.shape.copy()

        return cpCell


    def getSubCell(self,seq):

        subCell = self.copy()
        subCell.definition = seq.copy()
        subCell.shape = None
        subCell.surfaceList  = subCell.definition.getSurfacesNumbers()
        for s in tuple(subCell.surfaces.keys()) :
            if s not in subCell.surfaceList: del(subCell.surfaces[s])
                  
        return subCell    

    def split(self,nparts=2):

        if nparts == 1:
            return (self,None)
        terms,operador = self.getOuterTerms()
        nelemts = int(len(terms)/nparts)
        subDefList = []

        if operador == 'AND':
          for i in range(parts-1):
             newdef = ') ('.join(terms[i*nelemts:(i+1)*nelemts])
             newdef = '({})'.format(newdef)
             subDefList.append(newdef) 
          newdef = ') ('.join(terms[(nparts-1)*nelemts:])
          newdef = '({})'.format(newdef)
          subDefList.append(newdef)

        else:
          for i in range(nparts-1):
             newdef = '):('.join(terms[i*nelemts:(i+1)*nelemts])
             newdef = '({})'.format(newdef)
             subDefList.append(newdef)
          newdef = '):('.join(terms[(nparts-1)*nelemts:])
          newdef = '({})'.format(newdef)
          subDefList.append(newdef)

   
        subCellList=[] 
        for df in subDefList:
           subCell = self.copy()
           subCell.definition= cline(df)
           subCell.shape = None
           subCell.surfaceList  = subCell.definition.getSurfacesNumbers()
           for s in tuple(subCell.surfaces.keys()) :
               if s not in subCell.surfaceList: del(subCell.surfaces[s])
                  
           subCellList.append(subCell)    

        return subCellList,operador

    def getOuterTerms(self):
        if not self.__defTerms__ :
           self.__defTerms__,self.__operator__ = outterTerms(self.definition.str)
        return self.__defTerms__, self.__operator__
        
    def makeBox(self,boundBox):
        box_origin = FreeCAD.Vector(boundBox.XMin,boundBox.YMin,boundBox.ZMin)
        return Part.makeBox(boundBox.XLength,boundBox.YLength,boundBox.ZLength,box_origin )

    def buildShape(self,boundBox,force=False,surfTR=None,simplify=False,fuse=False):

        if self.shape is not None and not force: return
        if surfTR                  : self.transformSurfaces(surfTR)
        
        cutShape = BuildSolid(self,boundBox,simplify=simplify)
        
        if fuse or True:
          self.shape = FuseSolid(cutShape)
        else:
          self.shape = Part.makeCompound(cutShape)

    def buildSurfaceShape(self,boundBox):
        for s in self.surfaces.values():
           s.buildShape(boundBox)
        
    def transformSolid(self,matrix,reverse=False):
       if not self.shape : return 
       if reverse :
          self.shape = self.shape.transformGeometry(matrix.inverse())
       else:
          self.shape = self.shape.transformGeometry(matrix)

    def transformSurfaces(self,matrix):
        for s in self.surfaces.values():
            s.transform(matrix)
        
    def setSurfaces(self,Surfaces):
        if self.surfaces is not None : return
        self.surfaces = {}
        for s in self.surfaceList :
            self.surfaces[s] = Surfaces[s]

    def cleanUndefined(self):
       undefined = []
       for s in self.definition.getSurfacesNumbers() :
          if self.surfaces[s].params is None:
              undefined.append(s)
       if undefined:
           print('undefined surfaces',undefined)
           self.definition.removeSurface(undefined)

       for s in undefined:
           del (self.surfaces[s])
      
    def __setDefinition__(self,stringCell) :

       self.definition = stringCell.geom
       self.definition.remove_comments(full=True)
       self.definition.remove_cr()
       self.definition.remove_multispace()
       self.definition.remove_redundant()
       self.surfaceList  = self.definition.getSurfacesNumbers()
     

class Plane:
    def __init__(self,Id,params,tr=None):
        self.type = 'plane'
        self.id = Id
        self.shape = None
        self.params = params
        if tr :
           self.transform(tr)

    def copy(self):
        return Plane(self.id,self.params)
    
    def transform(self,matrix):
        v,d = self.params
        p = d*v  # vector p is d*plane normal
        v = matrix.submatrix(3).multVec(v)
        v.normalize()
        d = matrix.multVec(p)*v
        self.params = (v,d)

    def buildShape(self,boundBox):
        normal, p0 = self.params
        Box = FreeCAD.BoundBox(boundBox)
        Box.enlarge(10)

        pointEdge=[]
        for i in range(12):
           edge = Box.getEdge(i)
           p1 = normal.dot(edge[0])
           p2 = normal.dot(edge[1])
           d0 = p0 - p1
           d1 = p2 - p1
           if d1 != 0 :
             a = d0/d1
             if (a >= 0 and a <= 1):
                pointEdge.append(edge[0]+a*(edge[1]-edge[0]))
  
        if len(pointEdge) == 0 : return
        s = FreeCAD.Vector((0,0,0))
        for v in pointEdge :
           s = s + v
        s = s /  len(pointEdge) 
   
        vtxvec = []
        for v in pointEdge :
           vtxvec.append(v-s)

        X0 = vtxvec[0]
        Y0 = normal.cross(X0)

        orden=[]
        for i,v in enumerate(vtxvec):
          phi = np.arctan2(v.dot(Y0),v.dot(X0))
          orden.append((phi,i))
        orden.sort()
    
        self.shape = Part.Face( Part.makePolygon([ pointEdge[p[1]] for p in orden ], True))
        

class Sphere:
    def __init__(self,Id,params,tr=None):
        self.type = 'sphere'
        self.id = Id
        self.shape = None
        self.params = params
        if tr :
           self.transform(tr)

    def copy(self):
        return Sphere(self.id,self.params)
    
    def transform(self,matrix):
        p,R = self.params
        p = matrix.multVec(p)
        self.params = (p,R)    

    def buildShape(self,boundBox):
        origin,R = self.params
        self.shape = Part.makeSphere(R,origin)
     

class Cylinder:
    def __init__(self,Id,params,tr=None):
        self.type = 'cylinder'
        self.id = Id
        self.shape = None
        self.params = params
        if tr :
           self.transform(tr)

    def copy(self):
        return Cylinder(self.id,self.params)

    def transform(self,matrix):
        p,v,R = self.params
        v = matrix.submatrix(3).multVec(v)
        p = matrix.multVec(p)
        self.params = (p,v,R)
    
    def buildShape(self,boundBox):

        p, vec, r = self.params

        dmin = vec.dot(boundBox.getPoint(0)-p)
        dmax = dmin
        for i in range(1,8) :
           d = vec.dot(boundBox.getPoint(i)-p)
           dmin = min(d,dmin)
           dmax = max(d,dmax)

        height = dmax-dmin
        dmin -= 0.1*height
        dmax += 0.1*height
        height = dmax-dmin
   
        point = p + dmin * vec
        self.shape = Part.makeCylinder( r,height,point,vec,360)
        return

class Cone:
    def __init__(self,Id,params,tr=None):
        self.type = 'cone'
        self.id = Id
        self.shape = None
        self.params = params
        if tr :
           self.transform(tr) 

    def copy(self):
        return Cone(self.id,self.params)

    def transform(self,matrix):
        p,v,t,dbl = self.params
        v = matrix.submatrix(3).multVec(v)
        p = matrix.multVec(p)
        self.params = (p,v,t,dbl)
    
    def buildShape(self,boundBox):
        apex, axis, t, dblsht = self.params
        
        dmin = axis.dot(boundBox.getPoint(0)-apex)
        dmax = dmin
        for i in range(1,8) :
           d = axis.dot(boundBox.getPoint(i)-apex)
           dmin = min(d,dmin)
           dmax = max(d,dmax)

        length = max(abs(dmin),abs(dmax))
        R = length * t
        OneSheetCone = Part.makeCone( 0,R,length,apex,axis,360)
        if not dblsht :
           self.shape = OneSheetCone
        else:
           OtherSheet = Part.makeCone( 0,R,length,apex,-axis,360)
           DoubleSheetCone = OneSheetCone.fuse([OtherSheet])
           DoubleSheetCone.removeSplitter()
           self.shape = DoubleSheetCone  

class Torus:
    def __init__(self,Id,params,tr=None):
        self.type = 'torus'
        self.id = Id
        self.shape = None
        self.params = params
        if tr :
           self.transform(tr)
           
    def copy(self):
        return Torus(self.id,self.params)

    def transform(self,matrix):
        p,v,Ra,R = self.params
        v = matrix.submatrix(3).multVec(v)
        p = matrix.multVec(p)
        self.params = (p,v,Ra,R)
    
    def buildShape(self,boundBox):
        center, axis, majorR, minorR  = self.params  #Ra distance from torus axis; R radius of toroidal-cylinder
        self.shape = Part.makeTorus(majorR,minorR,center,axis)
      

class Undefined:
    def __init__(self,Id):
        self.type = 'Undefined'
        self.id = Id
        self.shape = None
        self.params = None

    def copy(self):
        return Undefined(self.id)

    def buildShape(self,boundBox):
        return
    
    def transform(self,matrix):
        return

def FuseSolid(parts):
    if (len(parts)) <= 1:
       if parts : 
          solid = parts[0]
       else:
          return None 
    else:
       try:
          fused = parts[0].fuse(parts[1:])
       except:
          fused = None

       if fused is not None:
         try : 
             refinedfused = fused.removeSplitter()
         except :
             refinedfused = fused
           
         if refinedfused.isValid() :
             solid = refinedfused
         else :
             if fused.isValid():
                solid = fused
             else:
                solid = Part.makeCompound(parts)
       else:
         print('parts',parts)
         solid = Part.makeCompound(parts)
           
    if solid.Volume < 0 : solid.reverse()
    return solid

src/GEOReverse/Modules/Objects.py
GEOUNED-code/GEOUNED
import string
version = '3.6'

def make_label(item):
    if 0 < len(item):
        return '{' + item + '}'
    else:
        return item


class SafeDict(dict):

    def __getitem__(self, item):
        return super(SafeDict, self).__getitem__(item) or ''

    def __missing__(self, key):
        return make_label(key)


# ' 1 #'
class PartialFormatter(string.Formatter):

    def format(*args, **kwargs):
        if not args:
            raise TypeError("descriptor 'format' of 'Formatter' object "
                            "needs an argument")
        self, args = args[0], args[1:]  # allow the "self" keyword be passed
        try:
            format_string, args = args[0], args[1:] # allow the "format_string" keyword be passed
        except IndexError:
            if 'format_string' in kwargs:
                format_string = kwargs.pop('format_string')
            else:
                raise TypeError("format() missing 1 required positional "
                                "argument: 'format_string'")
        return string.Formatter.vformat(self, format_string, args, SafeDict(kwargs))

    def _vformat(self, format_string, args, kwargs, used_args, recursion_depth, auto_arg_index=0):
        """Clone from Python3 version to fix Python2 mess with unnamed {} format specifiers"""
        if recursion_depth < 0:
            raise ValueError('Max string recursion exceeded')
        result = []
        for literal_text, field_name, format_spec, conversion in \
                self.parse(format_string):

            # output the literal text
            if literal_text:
                result.append(literal_text)

            # if there's a field, output it
            if field_name is not None:
                # this is some markup, find the object and do
                #  the formatting

                # handle arg indexing when empty field_names are given.
                if field_name == '':
                    if auto_arg_index is False:
                        raise ValueError('cannot switch from manual field '
                                         'specification to automatic field '
                                         'numbering')
                    field_name = str(auto_arg_index)
                    auto_arg_index += 1
                elif field_name.isdigit():
                    if auto_arg_index:
                        raise ValueError('cannot switch from manual field '
                                         'specification to automatic field '
                                         'numbering')
                    # disable auto arg incrementing, if it gets
                    # used later on, then an exception will be raised
                    auto_arg_index = False

                # given the field_name, find the object it references
                #  and the argument it came from
                obj, arg_used = self.get_field(field_name, args, kwargs)
                used_args.add(arg_used)

                # do any conversion on the resulting object
                obj = self.convert_field(obj, conversion)

                # expand the format spec, if needed
                if version == '2.7':
                    format_spec = self._vformat(
                        format_spec, args, kwargs,
                        used_args, recursion_depth-1,
                        auto_arg_index=auto_arg_index)
                else:
                    format_spec, auto_arg_index = self._vformat(
                        format_spec, args, kwargs,
                        used_args, recursion_depth-1,
                        auto_arg_index=auto_arg_index)
                # format the object and append to the result
                result.append(self.format_field(obj, format_spec))

        result = ''.join(result)

        if version == '2.7':
            return result
        else:
            return result, auto_arg_index

src/GEOReverse/Modules/Parser/PartialFormatter.py
GEOUNED-code/GEOUNED
# -*- coding: utf-8 -*-

"""
Functions for parsing MCNP input files.
"""

import re
import warnings
import os
from GEOReverse.Modules.Parser.PartialFormatter import PartialFormatter

version = '3.6'

try:
    # This clause define the fallback for cPickle, which is an accelerated
    # version of pickle in Python2. In Python3 the acceleration is considered
    # to be package-internal details, therefore the whole clause is an overkill
    # -- an accelerated version will be imported with pickle, if available.
    import cPickle
except ImportError:
    import pickle as cPickle

# integer with one prefix character
re_int = re.compile(r'\D{0,1}\d+')

# interior of square brackets for tally in lattices
re_ind = re.compile(r'\[.+\]', flags=re.DOTALL)

# repitition syntax of MCNP input file
re_rpt = re.compile(r'\d+[ri]', flags=re.IGNORECASE)

# imp or tmp parameters in cell card
re_prm = re.compile(r'((imp:n|imp:p|tmp)\s+\S+)')
re_prm = re.compile(r'[it]mp:*[npe]*[=\s]+\S+', flags=re.IGNORECASE)
re_prm = re.compile(r'([it]mp:*[npe]*[=\s]+)(\S+)', flags=re.IGNORECASE)

# fill keyword
re_fll = re.compile(r'\*{0,1}fill[=\s]+', flags=re.IGNORECASE)  # TODO: this will also match fill===


# If type specifier not given, any data type can be formatted:
def fmt_gen(s):
    return '{' + ':<{}'.format(len(s)) + '}'


fmt_d = fmt_gen
fmt_g = fmt_gen
fmt_s = fmt_gen

partial_formmatter = PartialFormatter()


class __CIDClass(object):
    """
    There are two levels of card types. 1-st level is purely defined by card
    position in the input file.  There can be:
          * message card
          * title card
          * cell card
          * surface card
          * data card.

    Data cards can be of different type that is defined by the card's first
    entry. Therefore data cards can be characterized by type of the 2-nd level:
          * m cards,
          * f cards,
          * etc.

    This class is to describe the 1-st level of card types.
    """
    # no-information cards
    comment = -1
    blankline = -2
    # card types in order they appear in MCNP input
    message = 1
    title = 2
    cell = 3
    surface = 4
    data = 5

    @classmethod
    def get_name(cls, cid):
        """
        Return the name of the card type by its index.
        """
        for k, v in list(cls.__dict__.items()):
            if '__' not in k and v == cid:
                return k
        else:
            print('No attribute with name', cid)
            raise ValueError()


CID = __CIDClass()


class Card(object):
    """
    Representation of a card.
    """
    def __init__(self, lines, ctype, pos, debug=None):

        # Original lines, as read from the input file
        self.lines = lines

        # card type by its position in the input. See CID class.
        self.ctype = ctype

        # True if self.lines has changed after initialization
        # used in remove_hash function
        self.cstrg = False

        # data card type. Defined from the get_values() method.
        # Has sense only to data cards (see ctype). For other card types
        # is None.
        self.dtype = None

        # Input file line number, where the card was found.
        self.pos = pos

        # File-like object to write debug info (if not None)
        self.debug = debug

        # template string. Represents the general structure of the card. It is
        # a copy of lines, but meaningful parts are replaced by format
        # specifiers, {}
        self.template = ''

        # List of strings represenging meaningful parts of the card. The
        # original multi-line string card is obtained as
        # template.format(*input)
        self.input = []

        # Dictionary of parts that are removed from input before processing it.
        # For example, repitition syntax (e.g. 5r or 7i) is replaced with '!'
        # to prevent its modification.
        self.hidden = {}

        # List of (v, t) tuples, where v -- value and t -- its type.
        self.values = []

        # geometry prefix and suffix
        # self.geom_prefix = ''
        # self.geom_suffix = ''

        # some properties defined on demand
        # cell properties
        self.__u = -1  # -1 means undefined. None -- not specified in input
        self.__f = -1  # fill
        self.__m = -1  # material
        self.__d = ''  # density
        self.__i = -1  # importances
        self.__cr = -1  # set of reference cells.
        # surface properties
        self.__st = ''  # '' means undefined.

        # Split card to template and meaningful part is always needed. Other
        # operations are optional.
        self.get_input()
        return

    def _get_value_by_type(self, t):
        """
        Returns the first value of type t found in self.values.
        """
        vl, tl = zip(*self.values)
        try:
            i = tl.index(t)
        except ValueError:
            return None
        finally:
            return vl[i]

    def _set_value_by_type(self, t, v):
        """
        Sets the first value of type t to v in self.values.
        """
        vl, tl = zip(*self.values)
        i = tl.index(t)
        self.values[i] = (v, t)

    @property
    def geom_prefix(self):
        return self._get_value_by_type('#gpr')

    @geom_prefix.setter
    def geom_prefix(self, value):
        return self._set_value_by_type('#gpr', value)

    @property
    def geom_suffix(self):
        return self._get_value_by_type('#gsu')

    @geom_suffix.setter
    def geom_suffix(self, value):
        return self._set_value_by_type('#gsu', value)

    def print_debug(self, comment, key='tihv'):
        d = self.debug
        if d:
            print('Line {}, {} card. {}'.format(self.pos,
                                                CID.get_name(self.ctype),
                                                comment), file=d)
            if 't' in key:
                print('    template:', repr(self.template), file=d)
            if 'i' in key:
                print('    input:   ', self.input, file=d)
            if 'h' in key:
                print('    hidden:  ', self.hidden, file=d)
            if 'v' in key:
                print('    values:  ', self.values, file=d)

    def get_input(self, check_bad_chars=False):
        """
        Recompute template, input and hidden attributes from lines
        """

        mline = ''.join(self.lines)
        if check_bad_chars:
            bad_chars = '\t'
            for c in bad_chars:
                if c in mline:
                    if self.debug:
                        self.print_debug('get_input: bad char in input cards',
                                         '')
                    else:
                        raise ValueError('Bad character in input file. ' +
                                         'Run with --debug option.')

        if self.ctype in (CID.comment, CID.blankline):
            # nothing to do for comments or blanklines:
            self.input = ''
            self.template = mline

        else:
            # TODO: protect { and } in comment parts of the card.
            tmpl = []  # part of template
            inpt = []  # input, meaningful parts of the card.
            if mline.split()[0][:2] == 'fc':
                # this is tally comment. It always in one line and is not
                # delimited by & or $
                i = mline[:80]
                t = mline.replace(i, '{}', 1)
                inpt = [i]
                tmpl = [t]
            else:
                for l in self.lines:
                    if is_commented(l):
                        tmpl.append(l)
                    else:
                        # entries optionally delimited from comments by $ or &
                        # requires that delimiters prefixed with space
                        d = index_(l, '$&')
                        # d1 = l.find(' $')
                        # d2 = l.find(' &')
                        # if -1 < d1 and -1 < d2:
                        #     # both & and $ in the line. Use the most left
                        #     d = min(d1, d2)
                        # elif d1 == -1 and d2 == -1:
                        #     # no delimiters at all, whole line is meaningful
                        #     # except the new-line char
                        #     d = len(l) - 1
                        # else:
                        #     # only one of delimiters is given.
                        #     d = max(d1, d2)
                        i = l[:d]
                        t = l[d:]
                        inpt.append(i)
                        tmpl.append(fmt_s(i) + t)
            self.input = inpt
            self.template = ''.join(tmpl)

            # TODO: dtype and name of the card can be defined already here.

        self.print_debug('get_input', 'ti')
        return

    def _protect_nums(self):
        """
        In the meaningful part of the card replace numbers that do not
        represent cell, surface or a cell parameter with some unused char.
        """

        inpt = '\n'.join(self.input)

        d = {}

        # in cell card:
        if self.ctype == CID.cell:  # and 'like' not in inpt:
            d['~'] = []  # float values in cells

            # Replace material density
            if 'like' not in inpt:
                tokens = inpt.replace('=', ' ').split()
                cell, mat, rho = tokens[:3]
                if int(mat) != 0:
                    for s in (cell, mat, rho):
                        inpt = inpt.replace(s, '~', 1)
                    inpt = inpt.replace('~', cell, 1)
                    inpt = inpt.replace('~', mat, 1)
                    d['~'].append(rho)

            # imp and tmp parameters:
            # print 're_prm: inp', repr(inpt)
            for s1, s2 in re_prm.findall(inpt):
                # print 're_prm: fnd', repr(s)
                d['~'].append(s2)
                inpt = inpt.replace(s1 + s2, s1 + '~', 1)

        # replace repitition syntax in junks:
        sbl = re_rpt.findall(inpt)
        if sbl:
            for s in sbl:
                inpt = inpt.replace(s, '!', 1)
            d['!'] = sbl

        if (self.ctype == CID.data and
                inpt.lstrip().lower()[0] == 'f' and
                inpt.lstrip()[1].isdigit()):
            # this is tally card. Hide indexes in square brackets
            sbl = re_ind.findall(inpt)
            if sbl:
                for s in sbl:
                    inpt = inpt.replace(s, '|', 1)
                d['|'] = sbl

        self.input = inpt.split('\n')
        self.hidden = d

        self.print_debug('_protect_nums', 'ih')
        return

    def get_values(self):
        """
        Replace integers in the meaningfull part with format specifiers, and
        populate the `values` attribute.
        """
        self._protect_nums()
        if self.ctype == CID.cell:
            inpt, vt = _split_cell(self.input, self)
            self.name = vt[0][0]
        elif self.ctype == CID.surface:
            inpt, vt, stype, scoef = _split_surface(self.input)
            self.TR = None
            if len(vt) > 1:
               if vt[1][1] == 'tr':
                  self.TR = vt[1][0] 
            self.stype = stype.lower()
            self.scoefs = scoef
            self.name = vt[0][0]
        elif self.ctype == CID.data:
            inpt, vt, dtype = _split_data(self.input)
            self.dtype = dtype
            if dtype == 'TRn':
                #print(vt,inpt)
                unit, inpt, fvals = _parse_tr(inpt)
                self.unit = unit
                vt += fvals
            if self.dtype is not None:
                self.name = vt[0][0]
        else:
            inpt = self.input
            vt = []

        self.input = inpt
        self.values = vt

        self.print_debug('get_values', 'iv')
        return

    def get_refcells(self):
        """
        Returns all cells used in definition of self.
        """
        if self.ctype != CID.cell:
            return None
        if self.__cr != -1:
            return self.__cr
        else:
            s = set()
            for v, t in self.values:
                if t == 'cel':
                    s.add(v)
            self.__cr = s
            return self.__cr

    def get_geom(self):
        """
        Returns part of the cell card describing geometry, as a (multiline)
        string.
        """
        p, s = self.geom_prefix, self.geom_suffix
        self.geom_prefix = '§'
        self.geom_suffix = '§'
        geom = self.card().split('§')[1]
        self.geom_prefix = p
        self.geom_suffix = s
        return geom

    def get_u(self):
        """
        Returns universe, the cells belongs to.
        """
        if self.ctype != CID.cell:
            return None
        if self.__u != -1:
            return self.__u
        else:
            # get it only once:
            for v, t in self.values:
                if t == 'u':
                    self.__u = v
                    break
            else:
                self.__u = None
            return self.__u

    def get_m(self):
        """
        For cell card return material number
        """
        if self.ctype != CID.cell:
            return None

        if self.__m != -1:
            return self.__m
        else:
            if 'like' in ''.join(self.input).lower():
                # material name should be given in another cell.
                pass
            for v, t in self.values:
                if t == 'mat':
                    self.__m = v
                    break
            else:
                # raise ValueError("Cell does not have material specs")
                self.__m = -2
            return self.__m

    def get_d(self):
        """
        For cell card return density
        """
        if self.__d != '':
            return self.__d

        if self.get_m() == 0:
            self.__d = 0
            return self.__d
        elif self.get_m() == -2:
            # this is like-but cell
            self.__d = -100.
            return self.__d
        else:
            # density entry is hidden in the input and available as the 1-st
            # entry in self.hidden dictionary.
            self.__d = float(self.hidden['~'][0])
            return self.__d

    def set_d(self, v):
        """
        Set density. Accespted are string represetntaion of a float.

        It is assumed that get_values() method is called before this.
        """
        if self.get_m() > 0:
            self.hidden['~'][0] = v
            self.__d = float(v)

    def get_f(self, newv=None, trsf=False):
        """
        Returns universe, the cell is filled with.
        """
        if self.ctype != CID.cell:
            return None

        if self.__f != -1 and newv is None:
            if trsf:
               params = [] 
               for v,t in self.values:
                 if t == '#tunit':
                    unit = v
                 elif t == 'tr':
                     params = [v]
                     break
                 elif t == '#tparam':
                     params.append(float(v))
               return self.__f,(unit,params)     
            else:
              return self.__f
        else:
            # get it only once:
            unit = ''
            params = []
            for i in range(len(self.values)):
                v, t = self.values[i]
                if t == 'fill':
                    if newv is not None:
                        v = newv
                        self.values[i] = (v, t)
                    self.__f = v
                    if trsf :
                       unit = self.values[i-1][0]
                       for v,t in self.values[i+1:]:  
                         if t == 'tr':
                            params = [v]
                            break
                         elif t == '#tparam':
                            params.append(float(v))  
                    break   
            else:
                self.__f = None
                
            if trsf :
               return self.__f,(unit,params)
            else:    
               return self.__f

    def get_imp(self, vals={}):
        """
        Returns importances, if explicitly specified in the cell card.
        """
        if self.ctype != CID.cell:
            return None

        if self.__i != -1 and not vals:
            return self.__i
        else:
            res = {}
            inpt = ' '.join(self.input).lower()
            for p in 'npe':
                key = 'imp:' + p

                s = inpt.split(key)
                if len(s) == 1:
                    # there is no key in the input line.
                    continue
                else:
                    n = s[0].count('~')
                    res[key] = float(self.hidden['~'][n])
                    if p in vals:
                        # change value only if necessary
                        if res[key] != vals[p]:
                            res[key] = vals[p]
                            self.hidden['~'][n] = str(vals[p])

                # for s in self.hidden.get('~', []):
                #     sl = s.lower()
                #     if key in sl:
                #         val = float(sl.replace(key, '').replace('=', ''))
                #         res[key] = val
            if not res:
                res['imp:n'] = 1
            self.__i = res
            return self.__i

    def remove_fill(self):
        """
        Removes the FILL= keyword of a cell card.

        This method must be called after get_values().
        """
        # Fill card is followed by one universe number and optionally by
        # transformation in parentheses. Optionally, the `fill` keyword can be
        # prefixed with asterisk

        # Two possibilites are here: (1) start from original line and clean out
        # everything related to FILL, or (2) modify already existing template,
        # values and input. In both cases, the new card should be checked for
        # empty lines.

        # Case (2): Modify existing input and values. All content after the
        # FILL keyword is parsed and thus is given in values, while input
        # provides placefor them. THus, simply replaceing values with spaces
        # will almost do the job. The remaining part -- the keyword itself that
        # is presented in input.

        # replace with spaces all FILL-related tokens
        vals = []  # new values list.
        oldv = self.values[:]
        state = 'before'
        while oldv:
            v, t = oldv.pop(0)
            if state == 'before' and t == 'fill':
                v = ' '
                state = 'afterU'
            elif state == 'afterU' and '(' in t:
                v = ' '
                state = 'after('
            elif state == 'after(':
                v = ' '
                if ')' in t:
                    state = 'after'
            vals.append((v, t))
        self.values = vals

        # Remove FILL from the input
        for n, i in enumerate(self.input):
            if 'fill' in i.lower():
                # This part of input contains the fill keyword. This keyword is
                # optionally prepended with an asterix and followed by a sign
                i = re_fll.sub(' ', i)
                self.input[n] = i
                break

        self.print_debug('remove_fill', 'iv')
        return

    def card(self, wrap=False, comment=True):
        """
        Return multi-line string representing the card.
        """
        if self.input:
            # put values back to meaningful parts:
            inpt = '\n'.join(self.input)
            inpt = inpt.format(*[t[0] for t in self.values])

            # put back hidden parts:
            for k, vl in list(self.hidden.items()):
                for v in vl:
                    inpt = inpt.replace(k, v, 1)

            inpt = inpt.split('\n')
            if not comment:
                return ' '.join(inpt)

            if wrap:  # and self.ctype != CID.title:
                indent = ' '*5
                if self.ctype == CID.title:
                    indent = 'c' + indent
                tparts = re.split(r'\{.*?\}', self.template)[1:]
                # print 'wrapped inp', repr(self.template)
                # print 'wrapped spl', repr(tparts)
                newt = ['']  # new template parts
                newi = []    # new input parts
                self.print_debug('card wrap=True', '')
                for i, t in zip(inpt, tparts):
                    self.print_debug('    ' + repr(i) + repr(t), '')
                    il = []
                    tl = [t]

                    # while len(i.rstrip()) > 79:
                    while len(i.rstrip()) > 80:
                        # first try to shift to left
                        if i[:5] == ' '*5:
                            i = ' '*5 + i.lstrip()
                        if len(i.rstrip()) > 79:
                            # input i must be wrapped. Find proper place:
                            for dc in ' :':
                                k = i.rstrip().rfind(dc, 0, 75)
                                if k > 6:
                                    il.append(i[:k])
                                    tl.append('\n')
                                    i = indent + i[k:]
                                    self.print_debug('card wrap=True' +
                                                     repr(il[-1]) +
                                                     repr(i), '')
                                    break
                            else:
                                # there is no proper place to wrap.
                                self.print_debug('Cannot wrap line ' +
                                                 repr(i), '')
                                warnings.warn('Cannot wrap card'
                                              ' on line {}'.format(self.pos))
                                break
                        else:
                            # input i fits to one line. Do nothing.
                            pass

                    newt += tl
                    newi += il + [i]
                tmpl = '{}'.join(newt)
                inpt = newi
            else:
                tmpl = self.template

            card = partial_formmatter.format(tmpl, *inpt)
            # card = tmpl.format(*inpt)
        else:
            card = self.template
        return card

    def remove_spaces(self):
        """
        Remove extra spaces from meaningful parts.
        """
        self.print_debug('before remove_spaces', 'i')
        if self.ctype in (CID.cell, CID.surface, CID.data):
            inpt = []
            for i in self.input:
                indented = i[:5] == ' '*5
                # leave only one sep. space
                i = ' '.join(i.split())
                i = i.strip()
                # spaces before/after some characters are not needed:
                for c in '):':
                    i = i.replace(' ' + c, c)
                for c in '(:':
                    i = i.replace(c + ' ', c)
                if indented:
                    i = ' '*5 + i
                inpt.append(i)
                self.print_debug(i, '')
            self.input = inpt
            self.print_debug('after remove_spaces', 'i')
        return

    def apply_map(self, f):
        """
        Replace Ni in self.values by Mi = f(Ni, Ti).
        """
        self.print_debug('before apply_map', 'vi')

        # u and fill should be renumberd in the same way, but types
        # must remain different, to let explicit u=0
        # self.values = map(lambda t: (f(t[0], t[1]), t[1]), self.values)
        newvals = []
        for t in self.values:
            if t[1] == 'fill':
                t1 = 'u'
            else:
                t1 = t[1]
            # newvals.append((f(t[0], t1), t[1]))
            if t1 in f:
                newval = f[t1](t[0])
            else:
                newval = t[0]
            newvals.append((newval, t[1]))
        self.values = newvals
        self.print_debug('after apply_map', 'vi')
        return


# def _parse_geom(geom):
#     """
#     Parse the geometry part of a cell card.
#     """
#     raise NotImplementedError()
#     t = geom.split()
#     vals = []
#     fmts = []
#
#     # cell name
#     js = t.pop(0)
#     geom = geom.replace(js, tp, 1)
#     vals.append((int(js), 'cel'))
#     fmts.append(fmt_d(js))
#
#     if 'like' in geom.lower():
#         # this is like-but syntax
#         pass
#     else:
#         # get material and density.
#         # Density, if specified in cells card, should be allready hidden
#         ms = t.pop(0)
#         if int(ms) == 0:
#             inpt = inpt.replace(ms, tp+tp , 1)
#         else:
#             inpt = inpt.replace(ms, tp, 1)
#             inpt = inpt.replace('~', '~'+tp, 1)
#         vals.append((int(ms), 'mat'))
#         fmts.append(fmt_d(ms))
#
#         # placeholder for geometry prefix
#         vals.append(('', '#gpr'))
#         fmts.append('{}')


def _split_cell(input_, self):
    """
    Replace integers in the meaningful parts of a cell card with format
    specifiers, and return a list of replaced values together with their types.

    """

    # meaningful parts together. Originally, they cannot have \n chars, since
    # all of them should land to the card template, therefore, after all
    # entries are replaced with format specifiers, it can be split back to a
    # list easily at \n positions.
    inpt = '\n'.join(input_)

    vals = []  # list of values
    fmts = []  # value format. It has digits, thus inserted into inpt later.
    tp = '_'   # temporary placeholder for format specifiers

    # Parse part before parameters. This is different for usual and like-but
    # syntax.  As result, all entries are replaced in inpt and i, the index
    # where parameter's part starts in inpt, is computed.

    if 'like ' in inpt.lower():

        # Get cell name
        t = inpt.split()
        js = t.pop(0)
        inpt = inpt.replace(js, tp, 1)
        vals.append((int(js), 'cel'))
        fmts.append(fmt_d(js))

        # Get reference cell name:
        t.pop(0)  # like
        js = t.pop(0)
        inpt = inpt.replace(js, tp, 1)
        vals.append((int(js), 'cel'))
        fmts.append(fmt_d(js))

        # compute i -- where first param token starts
        t.pop(0)  # but
        p0 = t.pop(0)
        i = inpt.index(p0)
        parm = [p0] + t

    else:
        # cell card has usual format.

        t = inpt.split()

        # Get cell name
        js = t.pop(0)
        inpt = inpt.replace(js, tp, 1)
        vals.append((int(js), 'cel'))
        fmts.append(fmt_d(js))

        # get material and density.
        # Density, if specified in cells card, should be allready hidden
        ms = t.pop(0)
        if int(ms) == 0:
            inpt = inpt.replace(ms, tp+tp, 1)
        else:
            inpt = inpt.replace(ms, tp, 1)
            inpt = inpt.replace('~', '~'+tp, 1)
        vals.append((int(ms), 'mat'))
        fmts.append(fmt_d(ms))

        # placeholder for geometry prefix
        vals.append(('', '#gpr'))
        fmts.append('{}')

        # Get geometry and parameters blocks. I assume that geom and param
        # blocks are separated by at least one space, so there will be an
        # element in t starting with alpha char -- This will be the first token
        # from the param block.
        geom = []
        parm = []
        while t:
            e = t.pop(0)
            if e[0].isalpha() or e[0] == '*':
                parm = [e] + t
                break
            else:
                geom.append(e)

        # print '_split_cell geom', geom, parm
        # replace integer entries in geom block:
        for s in re_int.findall(' '.join(geom)):
            # print 's from re_int', repr(s)
            # s is a surface or a cell (later only if prefixed by #)
            t = 'cel' if s[0] == '#' else 'sur'
            s = s if s[0].isdigit() else s[1:]
            f = fmt_d(s)
            inpt = inpt.replace(s, tp, 1)
            # print 't', repr(t)
            # print 's', repr(s)
            # print 'f', repr(f)
            # print repr(inpt)
            vals.append((int(s), t))
            fmts.append(f)

        # geometry suffix
        vals.append(('', '#gsu'))
        fmts.append('{}')
        # insert placeholder for geometry suffix
        if parm:
          inpt = inpt.replace(parm[0], '_' + parm[0], 1)

        # At this point all geom entries are replaced in inpt. The rest should
        # work only with the parm part of inpt. To ensure this, inpt is splitted
        # into inpt_geom and inpt_parm:
        if parm:
            i = inpt.index(parm[0])
        else:
            i = len(inpt)

    inpt_geom = inpt[:i]
    inpt_parm = inpt[i:]

    # print 'From parsing'
    # print repr(inpt)
    # print i
    # print repr(inpt_geom)
    # print repr(inpt_parm)
    # print parm

    # replace values in parameters block. Values are prefixed with = or space(s)
    # Note that tmp and imp values must be hidden
    t = ' '.join(parm).replace('=', ' ').split()  # get rid of =.
    while t:
        s = t.pop(0)
        # print '_split_cell s: ', repr(s)
        if s.lower() == 'u':
            vs = t.pop(0)
            vv = int(vs)
            vf = fmt_d(vs)
            vt = 'u'
            inpt_parm = inpt_parm.replace(vs, tp, 1)
            vals.append((vv, vt))
            fmts.append(vf)
        elif 'fill' in s.lower():
            # print '_split_cell: has fill!'
            # assume that only one integer follows the fill keyword, optionally
            # with transformation in parentheses.

            # set transformation dimension deg if *FILL or cos if FILL
            if '*' in s :
                vv = '*'
            else:
                vv = ''
            vf = fmt_s(vv)
            vt = '#tunit'
            vals.append((vv,vt))
            fmts.append(vf)
                
            vs = t.pop(0)
            # if transformation in parentheses follows the universe number
            # immediately, split this manually:
            if '(' in vs:
                i = vs.index('(')
                ttt = vs[i:]
                vs = vs[:i]
                # vs, ttt = vs.split('(')
                t.insert(0, ttt)    
            vv = int(vs)
            vf = fmt_d(vs)
            vt = 'fill'
            inpt_parm = inpt_parm.replace(vs, tp, 1)
            vals.append((vv, vt))
            fmts.append(vf)
            # fill value can be followed by transformation in parentheses
            # Fill value can be optionally followed by transformation number of
            # transformation parameters in parentheses
            if t and '(' in t[0]:
                vsl = []  # lists of strings, values, formats and types
                vvl = []
                vfl = []
                vtl = []

                # add opening parenthesis
                vsl.append('(')
                vvl.append('(')
                vfl.append(fmt_s('('))
                vtl.append('#(')  # #-types are internal, don't output in --mode info.
                t[0] = t[0].replace('(', '', 1)

                # add entries in parentheses and the closing parenthis
                while vsl[-1] != ')':
                    vs = t.pop(0)
                    if ')' in vs:
                        vs = vs.replace(')', '', 1)
                        if vs:
                            vsl.append(vs)
                            vvl.append(vs)
                            vfl.append(fmt_s(vs))
                            vtl.append('#tparam')
                        vsl.append(')')
                        vvl.append(')')
                        vfl.append(fmt_s(')'))
                        vtl.append('#)')
                    elif vs:
                        vsl.append(vs)
                        vvl.append(vs)
                        vfl.append(fmt_s(vs))
                        vtl.append('#tparam')

                # check if only one parameter in parenthethes -- it is tr
                # number, not tr parameter
                if len(vsl) == 3:
                    vvl[1] = int(vvl[1])
                    vfl[1] = fmt_d(vsl[1])
                    vtl[1] = 'tr'

                # add all strings, values, formats and types:
                for vs, vv, vf, vt in zip(vsl, vvl, vfl, vtl):
                    inpt_parm = inpt_parm.replace(vs, tp, 1) # TODO: here only parm part of inpt should be modified.
                    vals.append((vv, vt))
                    fmts.append(vf)

            # warn if there is possibility for an array following the fill
            # keyword:
            # TODO fill value can be an array
            if 'fill' == s.lower() and 'lat' in ''.join(parm).lower():
                print('WARNING: fill keyword followed by an array', end=' ')
                print('cannot be parsed')

    inpt = inpt_geom + inpt_parm

    # replace '_' with fmts:
    for f in fmts:
        inpt = inpt.replace(tp, f, 1)

    return inpt.split('\n'), vals


def _split_surface(input_):
    """
    Similar to _split_cell(), but for surface cards.
    """
    inpt = '\n'.join(input_)
    t = inpt.split()

    vals = []  # like in split_cell()
    fmts = []
    tp = '_'

    # get surface name:
    js = t.pop(0)
    if not js[0].isdigit():
        js = js[1:]
    inpt = inpt.replace(js, tp, 1)
    vals.append((int(js), 'sur'))
    fmts.append(fmt_d(js))

    # get TR or periodic surface:
    ns = t.pop(0)
    if ns[0].isdigit():
        # TR is given
        inpt = inpt.replace(ns, tp, 1)
        vals.append((int(ns), 'tr'))
        fmts.append(fmt_d(ns))
        st = t.pop(0)
    elif ns[0] == '-':
        # periodic surface
        ns = ns[1:]
        inpt = inpt.replace(ns, tp, 1)
        vals.append((int(ns), 'sur'))
        fmts.append(fmt_d(ns))
        st = t.pop(0)
    elif ns[0].isalpha():
        # ns is the surface type
        st = ns
    else:
        raise ValueError(input_, inpt, ns)

    # define coefficients
    scoef = list(map(float, t))

    for f in fmts:
        inpt = inpt.replace(tp, f, 1)

    return inpt.split('\n'), vals, st, scoef


def _get_int(s):
    r = ''
    for c in s:
        if r and c.isalpha():
            break
        elif c.isdigit():
            r += c
    return r


def _parse_tr(input_):
    """
    input_ should be already passed through _split_data()
    """
    inpt = '\n'.join(input_)
    inp1, inp2 = inpt.split(None, 1)
    if '*' in inp1:
        unit = '*'
    else:
        unit = ''

    svals = inp2.split()
    for s in svals:
        inp2 = inp2.replace(s, '{}', 1)

    fvals = [(float(s), 'float') for s in svals]
    return unit, (inp1 + ' ' + inp2).split('\n'), fvals


def _split_data(input_):
    inpt = '\n'.join(input_)
    t = inpt.split()

    vals = []
    fmts = []
    tp = '_'

    if 'tr' in t[0][:3].lower():
        # TRn card
        dtype = 'TRn'
        ns = _get_int(t[0])
        inpt = inpt.replace(ns, tp, 1)
        vals.append((int(ns), 'tr'))
        fmts.append(fmt_d(ns))
    elif (t[0][0].lower() == 'm' and
          'mode' not in t[0].lower() and
          'mesh' not in t[0].lower()):
        # This is the Mn, MTn or MPNn card
        ms = _get_int(t[0])
        inpt = inpt.replace(ms, tp, 1)
        vals.append((int(ms), 'mat'))
        fmts.append(fmt_d(ms))
        # additional tests to define data card type:
        if t[0][1].isdigit():
            dtype = 'Mn'
        elif t[0][1].lower() == 't':
            dtype = 'MTn'
        elif t[0][1].lower() == 'p':
            dtype = 'MPNn'
    elif t[0][0].lower() == 'f' and t[0][1].isdigit():
        # FN card
        dtype = 'Fn'
        ns = _get_int(t[0])  # tally number
        inpt = inpt.replace(ns, tp, 1)
        vals.append((int(ns), 'tal'))
        fmts.append(fmt_d(ns))

        # define type of integers by tally type:
        nv = int(ns[-1])
        if nv in [1, 2]:
            typ = 'sur'
        elif nv in [4, 6, 7, 8]:
            typ = 'cel'
        else:
            typ = ''

        if typ:
            # Lattice indices, surrounded by square brakets must allready be
            # hidden

            # Special treatment, if tally has 'u=' syntax.
            hasu = 'u' in inpt.lower() and '=' in inpt.lower()
            # find all integers -- they are cells or surfaces
            for s in re_int.findall(inpt):
                ss = s[1:]
                tpe = typ
                if hasu:
                    # ss can be universe. To distinguish this, one needs to look
                    # back in previous cheracters in c.
                    i1 = inpt.rfind(tp)
                    i2 = inpt.find(ss)
                    part = inpt[i1:i2]
                    while ' ' in part:
                        part = part.replace(' ', '')
                    if part[-2:].lower() == 'u=':
                        tpe = 'u'
                inpt = inpt.replace(ss, tp, 1)
                vals.append((int(ss), tpe))
                fmts.append(fmt_d(ss))
    elif 'fmesh' == t[0][:5].lower() and t[0][5].isdigit():
        # fmesh card
        dtype = 'fmesh'
        ns = _get_int(t[0])  # tally number
        inpt = inpt.replace(ns, tp, 1)
        vals.append((int(ns), 'tal'))
        fmts.append(fmt_d(ns))
    else:
        dtype = None

    for f in fmts:
        inpt = inpt.replace(tp, f, 1)

    return inpt.split('\n'), vals, dtype


def is_commented(l):
    """
    Return True if l is a commented line.
    """
    res = False

    # remove newline chars at the end of l:
    l = l.splitlines()[0]
    if 'c ' in l[0:6].lstrip().lower():
        res = True
        # print 'is_com "c "',
    elif 'c' == l.lower():
        res = True
        # print 'is_com "c"',
    # print 'is_com', res
    return res


def is_fc_card(l):
    """
    Return true, if line l is tally comment cards, fcN
    """
    return l.lstrip().lower()[:2] == 'fc'


def is_blankline(l):
    """
    Return True, if l is the delimiter blank line.
    """
    return l.strip() == ''

if version == '2.7':
    def get_cards(inp, debug=None, preservetabs=False):
        """
        Check first existence of a dump file

        If dump exists and it is newwer than the input file, read the dump file
        """
        from os import stat
        iname = inp
        dname = '.{}.~'.format(os.path.basename(inp))
        try:
            it = stat(iname).st_mtime
        except OSError as e:
            raise e

        try:
            dt = stat(dname).st_mtime
        except OSError:
            # print('No dump file exists')
            dt = it - 1.0
        if it < dt and debug is None:
            # print('Reading from dump')
            # dump is youger
            dfile = open(dname, 'r')
            cl = cPickle.load(dfile)
            for c in cl:
                yield c
        else:
            # print('Reading from input')
            cl = []
            for c in get_cards_from_input(inp, debug=debug, preservetabs=preservetabs):
                yield c
                cl.append(c)
        if debug is None:
            # otherwise the instances of c contain the file object, which
            # cannot be dumped.
            dfile = open(dname, 'w')
            cPickle.dump(cl, dfile)
else:
    def get_cards(inp, debug=None, preservetabs=False):
        """
        Check first existence of a dump file

        If dump exists and it is newwer than the input file, read the dump file
        """
        iname = inp
        for c in get_cards_from_input(inp, debug=debug, preservetabs=preservetabs):
            yield c


def index_(line, chars='$&'):
    """
    Find the first index of one of the chars in line.
    """
    r = re.compile('[{}]'.format(chars))
    m = r.search(line)
    if m:
        i = m.end() - 1
    else:
        i = len(line) - 1
    return i

def get_cards_from_input(inp, debug=None, preservetabs=False):
    """
    Iterable, return instances of the Card() class representing
    cards in the input file.

    inp -- is the filename.
    """

    def _yield(card, ct, ln):
        return Card(card, ct, ln, debug)

    def replace_tab(l, cln, preserve=False, ts=8):
        """
        Replace tabs as in MCNP5 (Vol II, Chapter 1 - Primer, I. MCNP INPUT FOR
        SAMPLE PROBLEM, A. INP File, p. 1-3)
        """
        if preserve:
            return l[:]
        else:
            while '\t' in l:
                i = l.index('\t')
                ii = (i // ts + 1) * ts - i
                print("c Line {}: tab replaced with {} spaces".format(cln + 1, ii))
                l = l[:i] + ' '*ii + l[i+1:]
            return l[:]

    cln = 0  # current line number. Used only for debug
    with open(inp, 'r') as f:
        # define the first block:
        # -----------------------

        # Next block ID
        ncid = 0  # 0 is not used in card ID dictionary CID.

        # Parse the 1-st line. It can be message, cell or data block.
        l = replace_tab(next(f), cln, preserve=preservetabs)
        cln += 1
        # kw = l.lower().split()[0]
        kw = l.lstrip()
        if 'message:' == kw[:8].lower():
            # read message block right here
            res = []
            while not is_blankline(l):
                res.append(l)
                l = replace_tab(next(f), cln, preserve=preservetabs)
                cln += 1
            yield _yield(res, CID.message, cln-1)  # message card
            yield _yield(l, CID.blankline, cln)      # blank line
            l = replace_tab(next(f), cln, preserve=preservetabs)
            cln += 1
            ncid = CID.title
        elif 'continue' == kw[:8].lower():
            # input file for continue job. Contains only data block.
            ncid = CID.data
        else:
            ncid = CID.title
        if ncid == CID.title:
            # l contains the title card
            yield _yield([l], ncid, cln)
            ncid += 1

        # read all other lines
        # --------------------

        # Line can be a continuation line in the following cases:
        #   * all lines in the message block, i.e. before the blank line
        #     delimiter
        #   * if line starts with 5 or more spaces,
        #   * if previous line ends with & sign.
        # Thus, the role of the current line (continuation or not) can be
        # defined by the line itself (5 spaces), or by previous lines (message
        # block or & sign). This can lead to inconsistency, when previous line
        # is delimited by &, but is followed by the blank line delimiter.  in
        # this case (rather theretical), blank line delimiter (as appeared more
        # lately) delimites the card from the previous line.
        cf = False  # continuation line flag. True only when prev. line has &.

        # Comment lines (CL) can be between cards or inside them. CL between two
        # cards are yielded as block of comments (although usually, CL are used
        # to describe cards that follow them).  CL inside a card will belong to
        # the card.

        card = []  # card is a list of lines.
        cmnt = []  # list of comment lines.
        for l in f:
            l = replace_tab(l, cln, preserve=preservetabs)
            cln += 1
            if is_blankline(l):
                # blank line delimiter. Stops card even if previous line
                # contains &
                if card:
                    # card can be empty, for example, when several empty lines
                    # are at the end of file
                    yield _yield(card, ncid, cln - len(card) - len(cmnt))
                if cmnt:
                    yield _yield(cmnt, CID.comment, cln - len(cmnt))
                    cmnt = []
                yield _yield(l, CID.blankline, cln)
                ncid += 1
                card = []
                if ncid == 6:
                    break
            elif l[0:5] == '     ' or cf:
                # l is continuation line.
                if cmnt:
                    card += cmnt  # prev. comment lines belong to this card.
                    cmnt = []
                card.append(l)
                cf = l[:index_(l)].find('&', 0, 81) > -1
            elif is_commented(l):
                # l is a line comment. Where it belongs (to the current card or
                # to the next one), depends on the next line, therefore, just
                # store temorarily.
                cmnt.append(l)
            else:
                # l is the 1-st line of a card. Return previous card and
                # comments
                if card:
                    yield _yield(card, ncid, cln - len(card) - len(cmnt))
                if cmnt:
                    yield _yield(cmnt, CID.comment, cln - len(cmnt))
                    cmnt = []
                card = [l]
                # if tally comment card, i.e. started with fc, the & character
                # does not mean continuation.
                cf = not is_fc_card(l) and l[:index_(l)].find('&', 0, 81) > -1
        if card:
            yield _yield(card, ncid, cln - len(card) - len(cmnt))
        if cmnt:
            yield _yield(cmnt, CID.comment, cln - len(cmnt))


def get_blocks(cards):
    """
    Return a dict of cards in blocks.
    """

    d = {}
    cbt = None  # current block type
    cbc = []    # current block cards
    for c in cards:
        if c.ctype == CID.blankline:
            d[cbt] = cbc
            cbt = None
            cbc = []
        elif c.ctype == CID.title:
            d[c.ctype] = [c]
        else:
            cbc.append(c)
            if cbt is None and c.ctype > 0:
                cbt = c.ctype
    if cbc:
        d[cbt] = cbc
    return d


def are_close_vals(x, y, re=1e-6, ra=0.):
    """
    Return True if x and y are closer then re or ra.
    """
    if abs(x - y) <= ra:
        r = True
    elif x != 0:
        r = abs((x - y)/x) <= re
    else:
        # y is not equal to x and x is 0 -> y is not 0.
        r = abs((x - y)/y) <= re
    return r


def are_close_lists(x, y, re=1e-6, pci=[]):
    """
    Return True if x and y are close but not equal.
    """
    if len(x) != len(y):
        res = False
        msg = 'Different length'

    if x == y:
        return True

    # pci -- list of indices that define elements of x and y to be checked for
    # proportionality only.
    if len(pci) == 0:
        # empty list means all x and y elements compare without arbitrary
        # normalization.
        xe = x[:]
        ye = y[:]
        xp = []
        yp = []
    else:
        if len(pci) % 2 == 1:
            # augment with len(x) +1
            pci = tuple(pci) + (len(x) + 1, )
        xe = []
        ye = []
        xp = []
        yp = []
        i = 0
        for i1, i2 in zip(pci[0::2], pci[1::2]):
            xe += x[i:i1]
            ye += y[i:i1]
            xp += x[i1:i2]
            yp += y[i1:i2]
            i = i2

    # normalize yp
    xpn = sum([e**2 for e in xp])
    ypn = sum([e**2 for e in yp])
    if xpn > 0 and ypn > 0:
        yp = [e*xpn/ypn for e in yp]

    msg = []
    res = []
    for xl, yl in zip([xe, xp], [ye, yp]):
        # compare xl and yl without normalization
        if xl == yl:
            res.append(True)
            msg.append('exact match')
        else:
            n = 0
            for xx, yy in zip(xl, yl):
                r = are_close_vals(xx, yy, re)
                if not r:
                    m = 'diff at {}'.format(n)
                    break
            else:
                m = 'all elements are close or equal'
                r = True
            res.append(r)
            msg.append(m)

        if not res[-1]:
            result = False
            break

    else:
        result = True
    return result


if __name__ == '__main__':
    pass

src/GEOReverse/Modules/Parser/parser.py
GEOUNED-code/GEOUNED
#
#   Conversion to MCNP v0.0 
#   Only one solid and planar surfaces
#
import FreeCAD
import BOPTools.SplitAPI
import math
import re
from GEOReverse.Modules.Utils.booleanFunction import BoolSequence

BoolVals = (None,True,False)

class CTelement:
   def __init__(self,val=None,S1=None,S2=None):
      self.diagonal = False
      self.S1       = S1
      self.S2       = S2
      if val is None :
         self.val = None
      else:
         if type(val) is int : 
            self.diagonal = True
            self.type  = None
         else:
            self.type  = val.count(0)
         self.val = val

   def getTranspose(self):
      if self.diagonal : return self.val
      return  CTelement((self.val[0],self.val[3],self.val[2],self.val[1]),self.S2,self.S1) 

   def getDependence(self):
      if self.diagonal : 
         if   self.val ==  0  : return True,False
         elif self.val == -1  : return 'Null',False
         else                 : return True,'Null'

      Ones = sum(self.val)
      if Ones == 4   : return None,None
      elif Ones == 3  :
         ind = self.val.index(0)
         if   ind == 0 : return False,None
         elif ind == 1 : return True ,None
         elif ind == 2 : return None ,True
         else          : return None ,False
      elif Ones == 2  :
         ind1 = self.val.index(0)
         ind2 = self.val.index(0,ind1+1)
         if   ind1 == 0 and ind2 == 1 : return 'Null',None
         elif ind1 == 2 and ind2 == 3 : return  None ,'Null'
         elif ind1 == 0 and ind2 == 3 : return  False,False
         else                         : return  True ,True
      elif Ones == 1  :  
         ind = self.val.index(1)
         if   ind == 0 : return True ,'Null'
         elif ind == 1 : return False,'Null'
         elif ind == 2 : return 'Null',False
         else          : return 'Null',True 
      else:
         return 'Null','Null'
          

class ConstraintTable(dict):

   def __init__(self):
       self.diagonal = None

   def __str__(self):

      varName = list(self.keys())

      # Constraint Table only diagonal terms
      if len(self[varName[0]]) == 1 :
          line = ''
          for name in varName :
             element = self[name][name]
             line += ' {:4d} : {}\n'.format(name,element.val)
          return line
           
      outstr = '  '
      for name in varName :
          outstr = outstr + ' {:3d}'.format(name)
      outstr = outstr + '\n'    

      for name1 in varName :
          line       = ' {:3d} '.format(name1)
          linenot    = '~{:3d} '.format(name1)
          for name2 in varName:
             elmt = self[name1][name2]  
             if elmt.diagonal :
               line    += ' {:>2d} '.format(elmt.val)
               linenot += '    '
             else:
               line    += ' {}{} '.format(elmt.val[0],elmt.val[1])
               linenot += ' {}{} '.format(elmt.val[3],elmt.val[2])
          outstr += line   +'\n'
          outstr += linenot+'\n'
      return outstr   

   def addElement(self,k1,k2,val):

       if k1 in self.keys(): 
          self[k1][k2] = val
       else:
          self[k1] = {k2:val}  

   def fillMissingElements(self):
      keys = list(self.keys())
      missing = []
      for i,k1 in enumerate(keys):
         for k2 in keys[i+1:]:
             if k2 in self[k1].keys():
                elmt = self[k1][k2]
                self[k2][k1] = elmt.getTranspose()
             else:
                missing.append((k1,k2))

      
      for k1,k2 in missing:
         diag1 = self[k1][k1]
         diag2 = self[k2][k2]
         new = combineDiagElements (diag1,diag2)
         new.S1 = k1
         new.S2 = k2
         self[k1][k2] = new
         self[k2][k1] = new.getTranspose()

   def getOutSurfaces(self):
      out = [] 
      for k in self.keys():
         if self[k][k].val != 0 :  out.append(k)
      return out       
     
   def getConstraintSet(self,valname):
       trueSet  = {}
       falseSet = {}
       TNull    = False
       FNull    = False
       for k in self.keys():
          TValue,FValue = self[valname][k].getDependence()
          if TValue == 'Null' :
               TNull = True
               TValue = None
          if FValue == 'Null' :
               FNull = True
               FValue = None
          if TValue is not None  : trueSet[k]  = TValue
          if FValue is not None  : falseSet[k] = FValue

       if TNull : trueSet  = None
       if FNull : falseSet = None
       return trueSet,falseSet 
     
   def solidInBox(self,Seq):             #  Sequence of the cell
      surfs = Seq.getSurfacesNumbers()
      if self.diagonal :
         seqValues = dict()
         for s in surfs : 
            seqValues[s] = BoolVals[self[s][s].val]
         # if evaluate   None  : Box intersection Cell != 0      Part of the cell in the box
         #               True  : Box intersection Cell == Box    Cell cover the full region of the box. Void cell doesn't exist
         #               False : Box intersection Cell == 0      Cell out of the box 
         return Seq.evaluate(seqValues)

      else:
         trueSet,falseSet = self.getConstraintSet(surfs[0])
         if trueSet is not None:  
             trueVal  = Seq.evaluate(trueSet) 
             if trueVal is None : return None
             if falseSet is not None:  
                falseVal = Seq.evaluate(falseSet) 
                if falseVal is None    : return None     # Part of the cell in the box
                if trueVal == falseVal : return trueVal  # True Cover full cell, False not in the box 
                else                   : return None     # Part of the cell in the box                
             else: 
                return trueVal
         elif falseSet is not None:  
             falseVal = Seq.evaluate(falseSet) 
             return falseVal
         else:
            print('Bad trouble surfaces {} is on none side of the box!!')
            return False       


def combineDiagElements(d1,d2):
    
    if   d1.val == 0 and d2.val == 0 : return CTelement((1,1,1,1))
    elif d1.val == 1 and d2.val == 0 : return CTelement((1,1,0,0))
    elif d1.val ==-1 and d2.val == 0 : return CTelement((0,0,1,1))
    elif d1.val == 0 and d2.val == 1 : return CTelement((1,0,0,1))
    elif d1.val == 0 and d2.val ==-1 : return CTelement((0,1,1,0))
    elif d1.val == 1 and d2.val == 1 : return CTelement((1,0,0,0))
    elif d1.val == 1 and d2.val ==-1 : return CTelement((0,1,0,0))
    elif d1.val ==-1 and d2.val == 1 : return CTelement((0,0,0,1))
    elif d1.val ==-1 and d2.val ==-1 : return CTelement((0,0,1,0))
      
def buildCTableFromSolids(Box,SurfInfo,option='diag'):

     if type(SurfInfo) is dict :
         surfaces = SurfInfo
         surfaceList = tuple(surfaces.keys())
     elif type(SurfInfo) is tuple :
         surfaceList,surfaces = SurfInfo
     else:
         surfaces    = SurfInfo.surfaces
         surfaceList = SurfInfo.surfaceList

     outSurfaces=[]
     for s in surfaceList: surfaces[s].buildShape(Box.BoundBox)

     CTable =  ConstraintTable()
     if option == 'diag':
        CTable.diagonal = True
     else:
        CTable.diagonal = False

     for i,s1 in enumerate(surfaceList):
         res,splitRegions = splitSolid_fast(Box,surfaces[s1],True)
         #res,splitRegions = splitSolid_fast(Box,Surfaces.getSurface(s1),True)
                    
         CTable.addElement(s1,s1,CTelement(res,s1,s1))
         if option == 'diag' :continue
         if splitRegions is None : continue  # loop, no region to be split by s2
         for j,s2 in enumerate(surfaceList[i+1:]) :
            posS1,negS1 = splitRegions

            pos0 = None
            for solid in posS1 :

                pos = splitSolid_fast(solid,surfaces[s2],False)
                    
                #pos = splitSolid_fast(solid,Surfaces.getSurface(s2),False)
                if pos == (1,1) : break   # s2 intersect S1 Region
                if pos0 is None :
                   pos0 = pos
                else:
                   if pos != pos0 :       # s1 regions are on both side of s2
                      pos = (1,1)
                      break
                   
            neg0 = None
            for solid in negS1 :
                #neg = splitSolid_fast(solid,Surfaces.getSurface(s2),False)
                neg = splitSolid_fast(solid,surfaces[s2],False)
                if neg == (1,1) : break   # s2 intersect S1 Region
                if neg0 is None :
                   neg0 = neg
                else:
                   if neg != neg0 :       # s1 regions are on both side of s2
                      neg = (1,1)
                      break       

            val = (pos[0],pos[1],neg[1],neg[0])
            CTable.addElement(s1,s2,CTelement(val,s1,s2))  


     # if some surfaces don't cross the box some elements in Constraint table are not filled
     if option != 'diag' : CTable.fillMissingElements()
     return CTable


def removeExtraSurfaces(CellSeq,CTable):
    # checking is make on solid cell definition to be removed from void cell
    outSurfaces = set(CTable.getOutSurfaces())
    changed = False
    newDef = BoolSequence(operator='OR')
  
    #Loop over all compound solids of the metaSolid
    if  CellSeq.level == 0 : 
        newSeq = BoolSequence(operator='AND')
        newSeq.append(CellSeq.copy())
        CellSeq.assign(newSeq)

    for subCell in CellSeq.elements:
        nullcell = False 
        chk = subCell.check()

        if chk == False :    # the cell doesn't exist
           nullcell = True
        elif chk == True :   # the cell describe the full universe
           newDef.elements = True
           newDef.level = 0
           return  newDef
       
        #if subcell has finite volume check it intersection with the box        
        if not nullcell :
           res = CTable.solidInBox(subCell)
           if res == None :   
                # subcell intersect the box
                # get the surfaces of the solids out of the box
                # get reduced definition
                removeSurf = outSurfaces & set(subCell.getSurfacesNumbers())
                for s in removeSurf:
                   val = True if CTable[s][s].val > 0 else False
                   subCell.substitute(s,val)
                if type(subCell.elements) is bool : 
                   if subCell.elements == False :   #  cell does not intersect void box
                      continue
                   else:                            # cell cover fully void box
                      newDef.elements = True
                      newDef.level = 0
                      return  newDef
                else:
                   newDef.append(subCell)

           elif res == True :
              # subcell cover the full box region Void cell doesn't exist
              newDef.elements = True
              newDef.level = 0
              return  newDef

    newDef.clean()

    return newDef      


def splitSolid_fast(solid,surf,box):

    if box :
       if surf.shape :
            comsolid = BOPTools.SplitAPI.slice(solid,[surf.shape],'Split', tolerance = 0)
       else:
            return  checkSign(solid,surf),None

       if len(comsolid.Solids) <= 1 :
          return checkSign(solid,surf),None
         # sgn = checkSign(solid,surf)   # if "box" and single object => the box is not split, surface s1 out of the box.
         # if sgn == 1 :                 # The sign is the side of surface s1 where the box is located 
         #    # return ((1,0),(0,0)),None  # return the diagonal element of the Constraint Table for s1
         #    return ((1,0),(0,0)),None  # return the diagonal element of the Constraint Table for s1
         # else: 
         #    return ((0,0),(0,1)),None

       else:
          posSol = []
          negSol = []
          for s in comsolid.Solids:
             sgn = checkSign(s,surf)
             if sgn == 1:
                 posSol.append(s)
             else:
                 negSol.append(s)
          return 0, (posSol,negSol) # return the diagonal element of the Constraint Table for s1, and solids to be split by s2       
          #return ((1,0),(0,1)), (posSol,negSol) # return the diagonal element of the Constraint Table for s1, and solids to be split by s2       

    else:
       # "not box" => return the position of the +/- region of s1 (the solid) with respect s2
       if surf.shape :
           dist = solid.distToShape(surf.shape)[0]
       else:
           dist = 1.
       if dist > 1e-6 : # face doesn't intersect solid
         sgn = checkSign(solid,surf)
         if sgn == 1:
            return  (1,0)   # values of s2 and -s2   "0" means this region doesn't exist
         else:
            return  (0,1)  
       else:
          return (1,1)   # values of s2 and -s2
          

               
               
# find one point inside a solid (region)
def point_inside(solid):

      point = solid.CenterOfMass
      if solid.isInside(point,0.0,False) : return point

      cut_line = 32
      cut_box  = 4

      v1=solid.Vertexes[0].Point
      for vi in range(len(solid.Vertexes)-1,0,-1):
        v2=solid.Vertexes[vi].Point
        dv= (v2-v1)*0.5
      
        n=1
        while True:
           for i in range(n): 
              point = v1 + dv *(1 + 0.5*i)
              if solid.isInside(point,0.0,False) : return point
           n=n*2
           dv=dv*0.5
           if (n > cut_line ) :
             break  

      BBox = solid.optimalBoundingBox(False)
      box = [BBox.XMin,BBox.XMax,
             BBox.YMin,BBox.YMax,
             BBox.ZMin,BBox.ZMax ]
      
      boxes,centers = CutBox(box)
      n = 0

      while True :
         for p in centers :
            pp = FreeCAD.Vector(p[0],p[1],p[2])
            if solid.isInside(pp,0.0,False) : return pp

         subbox = []
         centers = []
         for b in boxes :
            btab,ctab = CutBox(b)   
            subbox.extend(btab)
            centers.extend(ctab)
         boxes = subbox
         n = n + 1

         if (n == cut_box ) :
           print('Solid not found in bounding Box (Volume : {})'.format(solid.Volume))
           return None

# divide a box into 8 smaller boxes
def CutBox(Box):
     xmid = (Box[1]+Box[0])*0.5
     ymid = (Box[3]+Box[2])*0.5
     zmid = (Box[5]+Box[4])*0.5

   
     b1 = (Box[0],xmid,Box[2],ymid,Box[4],zmid)
     p1 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b2 = (xmid,Box[1],Box[2],ymid,Box[4],zmid)
     p2 = (0.5*(xmid+Box[1]), 0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b3 = (Box[0],xmid,ymid,Box[3],Box[4],zmid)
     p3 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))

     b4 = (xmid,Box[1],ymid,Box[3],Box[4],zmid)
     p4 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))
      
     b5 = (Box[0],xmid,Box[2],ymid,zmid,Box[5])
     p5 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b6 = (xmid,Box[1],Box[2],ymid,zmid,Box[5])
     p6 = (0.5*(xmid+Box[1]),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b7 = (Box[0],xmid,ymid,Box[3],zmid,Box[5])
     p7 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))
     
     b8 = (xmid,Box[1],ymid,Box[3],zmid,Box[5])
     p8 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))

     return (b1,b2,b3,b4,b5,b6,b7,b8),(p1,p2,p3,p4,p5,p6,p7,p8)        


def checkSign(solid,surf):

   point = point_inside(solid)

   if surf.type == 'plane':
       normal,d = surf.params
       r = point - d*normal
       if normal.dot(r) > 0 :
           return 1
       else:
           return -1

   elif surf.type == 'cylinder':
       center,axis,radius = surf.params
       r = point - center
       L2 = r.Length*r.Length
       z  = axis.dot(r)
       z2 = z*z
       R2 = radius*radius
       if L2-z2 > R2 :
           return 1
       else:
           return -1

   elif surf.type == 'sphere':
       center,radius = surf.params
       r = point - center
       if r.Length > radius :
           return 1
       else:
           return -1  

   elif surf.type == 'cone':
       apex,axis,t,dbl = surf.params
       r = point - apex
       r.normalize()
       z  = axis.dot(r)
       alpha = math.acos(z)

       if alpha > math.atan(t) :
           return 1
       else:
           return -1      

   elif surf.type == 'torus':
       center,axis,Ra,R = surf.params
       r = point - center
       v = axis.cross(r)
       if v.Length > 1e-8 :
         v.normalize()
         t = v.cross(axis)
         r = r + t * Ra 
       if r.Length > R:
          return 1
       else: 
          return -1


src/GEOReverse/Modules/Utils/BooleanSolids.py
GEOUNED-code/GEOUNED
import re
mostinner=re.compile(r"\([^\(^\)]*\)")                                      # identify most inner parentheses
number   =re.compile(r"(?P<value>[-+]?\d+)")
mix      =re.compile(r"(?P<value>([-+]?\d+|\[0+\]))")
TFX      =re.compile(r"(?P<value>[FTXo]+)")
PValue   =re.compile(r"P\d+")
NValue   =re.compile(r"N\d+")
conversion = {'T':True,'F':False,'X':None}

class BoolSequence:
    def __init__(self,definition=None,operator=None) :
        if definition :
            self.elements = []
            self.setDef(definition)
        else:    
            self.elements = []
            self.operator = operator
            self.level    = 0

    def __str__(self):
       out='{}['.format(self.operator)
       if type(self.elements) is bool : return ' True ' if self.elements else ' False ' 
       for e in self.elements:
          if type(e) is int or type(e) is bool or type(e) is str : 
             out += ' {} '.format(e)
          else :
             out += e.__str__()

       out += '] '
       return out

    def append(self,*seq):
       for s in seq:
         if type(s) is int :
             level = -1
         else:
             level = s.level 
             if type(s.elements) is bool:
               if self.operator == 'AND' and s.elements == False  or\
                  self.operator == 'OR'  and s.elements == True   : 
                  self.level = 0
                  self.elements = s.elements 
                  return
               else:
                  continue

         self.elements.append(s)
         self.level = max(self.level,level+1)

    def assign(self,Seq):
        self.operator = Seq.operator
        self.elements = Seq.elements
        self.level    = Seq.level 

    def copy(self):
        cp = BoolSequence()
        cp.operator= self.operator
        cp.level = self.level
        if type(self.elements) is bool:
           cp.elements = self.elements
        else:
           for e in self.elements:
              if type(e) is int :
                 cp.elements.append(e)
              else:
                 cp.elements.append(e.copy())
        return cp        
        
    def getComplementary(self):

       c = BoolSequence(operator=self.compOperator())
       c.level = self.level

       if self.level == 0:
          for e in self.elements:
             c.elements.append(-e) 
          return c
       else:
         self.groupSingle()
         for e in self.elements:
            c.elements.append( e.getComplementary() )
         return c        

                
    def compOperator(self):
       if self.operator == 'AND': 
          return 'OR'
       else:
          return 'AND'


    def simplify(self,CT,loop=0):
       surfNames = self.getSurfacesNumbers()
       if not surfNames : return
   #   print(CT)
   #    print('start',self)
       newNames = surfNames
       simplified = False
       for valname in surfNames:
   #      print('factorise',valname)
          if valname in newNames: 
             chg = self.factorize(valname,CT)
             simplified = simplified or chg
             newNames = self.getSurfacesNumbers()
       self.joinOperators()
       
       if self.level == 0 or (self.elements) is bool : return

       # new temp para ver como va
       #for e in self.elements:
       #   if type(e) is not int:
       #     e.simplify(CT)
       #return
       # end new


       if loop == 0:
          ANDSeq = BoolSequence(operator=self.operator)
          ORSeq  = BoolSequence(operator=self.operator)
          for e in self.elements:
             if e.operator == 'AND':
                ANDSeq.append(e) 
             else:
                ORSeq.append(e) 

          ANDSeq.simplify(CT,loop=1)
          ORSeq.simplify(CT,loop=1)
          newSeq = BoolSequence(operator=self.operator)
          if ANDSeq.elements: newSeq.append(ANDSeq) 
          if ORSeq.elements : newSeq.append(ORSeq) 
          newSeq.joinOperators()
          self.assign(newSeq)
       else:
          for e in reversed(self.elements):
              e.simplify(CT,loop=0)
              if type(e.elements) is bool:
                 if self.operator == 'AND' and e.elements == False or \
                    self.operator == 'OR' and e.elements == True  : 
                    self.elements = e.elements
                    self.level = 0
                    break
                 else:
                    self.elements.remove(e)

          if self.elements == []:
             self.level = 0
             if self.operator == 'AND' : 
                self.elements = True 
             else:
                self.elements = False

     # check if level 0 sequence have oposite value a & -a = 0  , a|-a = 1
     # return the value of the sequence None(unknown), True, False
    def check(self):
       if self.level == 0:
          if type(self.elements) is bool : return self.elements

          signedSurf = set(self.elements)
          surfname  = self.getSurfacesNumbers()
          if len(signedSurf) == len(surfname) :  return None  # means same surface has not positive and negative value
          elif self.operator == 'AND' :          return False
          else:                                  return True
       else:
           if type(self.elements) is bool : return self.elements

           self.groupSingle()
           noneVal = False
           for e in self.elements:
              res = e.check()
              if res is None : noneVal = True
              elif self.operator == 'AND' and res == False : return False
              elif self.operator == 'OR'  and res == True  : return True

           if    noneVal :                return None
           elif  self.operator == 'AND' : return True
           else:                          return False


    def substitute(self,var,val):
       if   type(self.elements) is bool: return
       name = abs(var)
       ic = len(self.elements)
       for e in reversed(self.elements):
          ic -= 1
          if type(e) is int:
             if abs(e) == name :
                if type(val) is int : 
                   if name == e : self.elements[ic] = val
                   else         : self.elements[ic] = -val

                else :
                   if name == e : boolValue = val
                   else         : boolValue = not val

                   if self.operator == 'AND' and not boolValue :
                        self.elements = False
                        self.level    = 0
                        return
                   elif self.operator == 'OR' and boolValue :
                        self.elements = True
                        self.level    = 0
                        return
                   else:
                        self.elements.remove(e)
          else:
             e.substitute(var,val) 
 
       self.clean()
       self.levelUpdate()

    def removeSurf(self,name):
       if   type(self.elements) is bool: return
       ic = len(self.elements)
       for e in reversed(self.elements):
          ic -= 1
          if type(e) is int:
             if e == name :
                if self.operator == 'AND'  :
                     self.elements.remove(e)
                elif self.operator == 'OR'  :
                     self.elements = True
                     self.level    = 0
                     return
          else:
             e.removeSurf(name) 
 
       self.clean()
       self.levelUpdate()



    # remove sequence whom elements are boolean values instead of list
    def clean(self):   
        if type(self.elements) is bool : return self.elements
        for e in reversed(self.elements) :
           if type(e) is int : continue
           eVal = e.clean()
           if type(eVal) is bool:
              if eVal and self.operator == 'OR'  : 
                 self.elements = True
                 self.level = 0
                 return True
              elif not eVal and self.operator == 'AND' : 
                 self.elements = False
                 self.level = 0
                 return False
              self.elements.remove(e)

        if self.elements == [] :
           if self.operator == 'OR' : self.elements = False
           else                     : self.elements = True
           self.level = 0
           return self.elements
        else:
           return None 


    # join redundant operators in sequence
    def joinOperators(self):
        if self.level == 0 : return
        if type(self.elements) is bool: return
        self.groupSingle()
        ANDop = []
        ORop  = []
     
        for e in self.elements:
           if e.operator == 'AND': ANDop.append(e) 
           else                  : ORop.append(e)

        if   len(ANDop) > 1  and self.operator == 'AND':
           newSeq = BoolSequence(operator='AND')
           for s in ANDop :
             newSeq.elements.extend(s.elements)
             self.elements.remove(s)
           newSeq.levelUpdate()
           self.append(newSeq)

            
        elif len(ORop)  > 1  and self.operator == 'OR':
           newSeq = BoolSequence(operator='OR')
           for s in ORop :
             newSeq.elements.extend(s.elements)
             self.elements.remove(s)
           newSeq.levelUpdate()
           self.append(newSeq)

        if self.level > 0  and len(self.elements)==1 :
           self.operator = self.elements[0].operator
           self.elements = self.elements[0].elements
           self.level -= 1
           self.joinOperators()
       
        if self.level == 0 : return
        for e in self.elements:
           e.joinOperators()



    def factorize(self,valname,CT=None):

        if CT is None :
           trueSet  = {abs(valname) : True }
           falseSet = {abs(valname) : False }
        else:
           trueSet,falseSet =  CT.getConstraintSet(valname)

        if trueSet is None: 
             self.substitute(valname,False)
             return True

        if falseSet is None: 
             self.substitute(valname,True)
             return True

        funcVal = self.evaluate(trueSet,CT)
        if funcVal == False :   
            newSeq = BoolSequence(operator='AND')
            #self.substitute(valname,False)
            for name,value in falseSet.items():
               self.substitute(name,value)
            newSeq.append(-valname,self.copy())
            self.assign(newSeq)
            return True
        elif funcVal == True:
            newSeq = BoolSequence(operator='OR')
            #self.substitute(valname,False)
            for name,value in falseSet.items():
               self.substitute(name,value)
            newSeq.append(valname,self.copy())
            self.assign(newSeq)
            return True


        funcVal = self.evaluate(falseSet,CT)
        if funcVal == False :   
            newSeq = BoolSequence(operator='AND')
            #self.substitute(valname,True)
            for name,value in trueSet.items():
               self.substitute(name,value)
            newSeq.append(valname,self.copy())
            self.assign(newSeq)
            return True
        elif funcVal == True:
            newSeq = BoolSequence(operator='OR')
            #self.substitute(valname,True)
            for name,value in trueSet.items():
               self.substitute(name,value)
            newSeq.append(-valname,self.copy())
            self.assign(newSeq)
            return True

        return False
  

    def evaluate_newbad(self,valueSet,CT=None):

        if type(self.elements) is bool : return self.elements
        self.groupSingle()
        newSeq = self.copy()
        for name,value in valueSet.items():
            newSeq.substitute(name,value)
        
        if type(newSeq.elements) is bool :
            return newSeq.elements
        else :
            surfNames = newSeq.getSurfacesNumbers()        
            valname = surfNames[0]
            if CT is None :
                trueSet  = {abs(valname) : True }
                falseSet = {abs(valname) : False }
            else:
                trueSet,falseSet =  CT.getConstraintSet(valname)
                 
            if trueSet is None:
                trueVal = True
            else:       
                trueVal  = newSeq.evaluate(trueSet,CT)
                if trueVal is None : return None
               
            if falseSet is None:
                falseVal = False
            else:       
                falseVal  = newSeq.evaluate(falseSet,CT)
                if falseVal is None : return None
                    
            if trueVal != falseVal :
                return None
            else:
                return trueVal 


    def evaluate(self,valueSet,CT=None):
        if type(self.elements) is bool : return self.elements
        self.groupSingle()
        op = self.operator
        noneVal = False
        for e in self.elements:
            if type(e) is int:
               key = abs(e)
               if key in valueSet.keys():
                    val = valueSet[key]
               else:
                    val = None
                    
               if val is not None : 
                  if e < 0 : val = not val
               else:
                  noneVal = True
            else:
               val = e.evaluate(valueSet)

            if val is None : noneVal = True
            elif op == 'AND' and val == False : return False
            elif op == 'OR'  and val == True  : return True

        if noneVal : 
           return None      
        if op == 'AND' :
           return True
        else:     
           return False
        
    def setDef(self,expression):
       terms,operator = outterTerms(expression)
       self.operator = operator
       self.level = 0
       for t in terms :
         if isInteger(t) :
            self.elements.append(int(t.strip('(').strip(')')))
         else:
            x = BoolSequence(t) 
            self.level = max(x.level+1,self.level)
            self.elements.append(x)
       self.groupSingle()     

    def groupSingle(self):
       if self.level == 0 : return
       if type(self.elements) is bool : return
       group = []
       for e in reversed(self.elements):
         if type(e) is int : 
            group.append(e)
            self.elements.remove(e)
         elif e.level==0 and len(e.elements) == 1 :
            group.append(e.elements[0])
            self.elements.remove(e)
            

       if not group : return
       seq = BoolSequence()
       seq.elements.extend(group)
       seq.operator = self.operator
       seq.level = 0
       self.elements.insert(0,seq)

    def sameOperator(self):
       if self.level == 0 : return
       if type(self.elements) is bool : return

       changed = []
       new = []
       for i,e in enumerate(self.elements):
          if e.operator == self.operator:
             changed.append(i) 
             for s in e.elements: 
                 if type(s) is int:
                    new.append(BoolSequence(str(s)))
                 else:
                    new.append(s)

       for i in reversed(changed):
         del(self.elements[i])
       self.append(*new)
       self.levelUpdate()

       return

    def getSurfacesNumbers(self):
        if type(self.elements) is bool : return tuple()
        surf = set()
        for e in self.elements:
           if type(e) is int :
                surf.add(abs(e))
           else:
                surf.update(e.getSurfacesNumbers())
        return tuple(surf)             

    def levelUpdate(self):
       if type(self.elements) is bool :
          self.level = 0
          return

       newlev = 0
       for e in self.elements:
          if type(e) is int :
             lev=0
          else:
             lev=e.level+1
          newlev = max(lev,newlev)
       self.level = newlev


def outterTerms(expression,value='number'):
      if value == 'number' :
          #reValue = number
          reValue = mix
          nullVal = '0'
      else:
          reValue = TFX
          nullVal = 'o'
          
      expr = expression
      

      # Loop until no redundant parentheses are found
      cont = True
      
      while cont:
        # Loop over most inner parentheses
        pos = 0
        cont = False
        while True :
          m = mostinner.search(expr,pos)
          if not m : break
          cont = True
          if redundant(m,expr):
             # remove redundant parentheses
             expr = expr[:m.start()]+ ' ' + expr[m.start()+1:m.end()-1]+ ' ' + expr[m.end():]
          else:
             # replace no redundant parentheses by 0 and : by ;
             zeros = '[' + nullVal* (m.end()-m.start()-2) + ']' 
             expr = expr[:m.start()] + zeros + expr[m.end():]

          pos = m.end()

      if ':' in expr :
          terms = []
          pos = 0
          while True :
              newpos = expr.find(':',pos)
              if newpos == -1 :
                  terms.append(expression[pos:].strip())
                  break
              terms.append(expression[pos:newpos].strip())
              pos = newpos + 1                       
          return (terms,'OR')
      else:
          terms = []
          pos = 0
          while True:
              m = reValue.search(expr,pos)
              if not m : break
              terms.append(expression[m.start():m.end()])
              pos = m.end()                          
          return (terms,'AND')
        

def redundant(m,geom):
   """ check if the inner parentheses are redundant """
   term = m.group()

   # Find first valid character at the left of the  parenthese
   leftOK= True
   left = m.start()-1
   while left > -1:
       if geom[left] in ('\n','C','$',' '):
          left -= 1
       else:
          if geom[left] not in ('(',':') : leftOK  = False
          break

  # check if no ':' (or) are inside the parenthese
  # if not, parentheses are redundants
   if (term.find(':') == -1) : return True

  # Find first valid character at the right of the  parenthese
   rightOK= True
   right = m.end()
   while right < len(geom)  :
       if geom[right] in ('\n','C','$',' '):
          right += 1
       else:
          if geom[right] not in (')',':') : rightOK  = False
          break

  # if parentheses are like:
  # {( or : } ( ....... ) {) or :}
  # parentheses are redundants

   if leftOK and rightOK :
       return True
   else:
       return False

def isInteger(x):
    try :
      int(x.strip('(').strip(')'))
      return True
    except:
      return False  

src/GEOReverse/Modules/Utils/booleanFunction.py
GEOUNED-code/GEOUNED
import FreeCAD, Part
import BOPTools.SplitAPI
from GEOReverse.Modules.buildSolidCell import FuseSolid
from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids
from GEOReverse.Modules.Utils.booleanFunction import BoolSequence



def buildCAD(UnivCell,data,config):

    UniverseCut = True 
    if 'Ustart'   not in config.keys(): config['Ustart'] = 0
    if 'levelMax' not in config.keys(): config['levelMax'] = 'all'
    UnivCell.name = 0
    UnivCell.Fill = config['Ustart']
    
    # read all surfaces definition
    modelSurfaces        = data.GetSurfaces(scale=10) # scale change cm in mcnp to mm in CAD Obj
    
    # read Cells and group into universes
    print(config)
    levels,UniverseCells,modelSurfaces = data.GetFilteredCells(modelSurfaces,config)

    # assign to each cell the surfaces belonging to the cell
    AssignSurfaceToCell(UniverseCells,modelSurfaces) 

#    print(UniverseCells[0][120].definition.str)
#    print(UniverseCells[0][120].surfaces)
#    CT=buildCTableFromSolids(UnivCell.shape,UniverseCells[0][70],option='full')
#    print(CT)
#    simply = BoolSequence(UniverseCells[0][70].definition.str)
#    print('antesSimply',simply)
#    simply.simplify(CT)
#    print('despues',simply)
    #exit()
        

    # dictionnary of the cells filled with a given Universe U
    # universeContainers = GetUniverseContainers(levels,UniverseCells)

    UnivCell.level = None
    levelMax = config['levelMax']
    Ustart   = config['Ustart']
    if levelMax == 'all':
        levelMax = len(levels)
       
    for lev,Univ in levels.items():
       if Ustart in Univ :
           UnivCell.level = lev-1
           break
    startInfo =(Ustart,levelMax)
    
    return BuildUniverse(startInfo,UnivCell,UniverseCells,universeCut=UniverseCut) 
     

def interferencia(container,cell,mode='slice'):

    if mode == 'common':
        return cell.shape.common(container.shape)

    Base = cell.shape
    Tool = (container.shape,)
    
    solids = BOPTools.SplitAPI.slice(Base,Tool, 'Split',tolerance = 0).Solids
    cellParts = []
    for s in solids:
        if container.shape.isInside(s.CenterOfMass,0.0,False) : cellParts.append(s)
 
    if not cellParts :
        return cell.shape
    else:
        return FuseSolid(cellParts)    
        
def AssignSurfaceToCell(UniverseCells,modelSurfaces):

    for Uid,uniCells in UniverseCells.items():
        for cId,c in uniCells.items():
           c.setSurfaces(modelSurfaces)

def GetUniverseContainers(levels,Universes):
    Ucontainer = {}
    for lev in range(1,len(levels)):
        for U,name in levels[lev]:
            UFILL = Universes[U][name].FILL
            if UFILL in Ucontainer.keys() :
               Ucontainer[UFILL].append((U,name,lev))
            else:
               Ucontainer[UFILL] = [(U,name,lev)]        
    return Ucontainer           

def BuildUniverse(startInfo,ContainerCell,AllUniverses,universeCut=True,duplicate=False):
    CADUniverse=[]

    Ustart,levelMax = startInfo
    Universe = AllUniverses[Ustart]
    
    print('Build Universe {} in container cell {}'.format(ContainerCell.FILL,ContainerCell.name))       
    fails = []
    for NTcell in Universe.values():
        if duplicate:
           if NTcell.shape :
              buildShape = False
              if ContainerCell.CurrentTR : 
                   cell = NTcell.copy()
                   cell.transformSolid(ContainerCell.CurrentTR)
              else:
                   cell = NTcell
           else:
              CTRF = None
              buildShape = True
              if ContainerCell.CurrentTR: 
                 CC = ContainerCell.copy()
                 CC.transformSolid(CC.CurrentTR,reverse=True)
              else:
                 CC = ContainerCell
        else:
           CTRF = ContainerCell.CurrentTR
           CC = ContainerCell
           buildShape = True
           NTcell = NTcell.copy()

        if buildShape: 
           print('Level :{}  build Cell {} '.format(CC.level + 1,NTcell.name))
           if type(NTcell.definition) is not BoolSequence:
               NTcell.definition = BoolSequence(NTcell.definition.str)

           #bBox = NTcell.getBoundBox(ContainerCell.shape.BoundBox)
           bBox = ContainerCell.shape.BoundBox
           #NTcell.buildShape(bBox,surfTR=CTRF,simplify=False,box=False)
           NTcell.buildShape(bBox,surfTR=CTRF,simplify=False)
         
          # try:
          #    NTcell.buildShape(CC.shape.BoundBox,surfTR=CTRF,simplify=False)
          # except:
          #    fails.append(NTcell.name)
       
           if NTcell.shape is None : continue

           if duplicate :
               if ContainerCell.CurrentTR : 
                  cell = NTcell.copy()
                  cell.transformSolid(ContainerCell.CurrentTR)
               else:
                  cell = NTcell
           else:
               cell = NTcell

  
        if universeCut and ContainerCell.shape : cell.shape = interferencia(ContainerCell,cell)
        
        if not cell.FILL or ContainerCell.level + 1 == levelMax:
           CADUniverse.append(cell)
        else:
           if ContainerCell.CurrentTR :
                cell.CurrentTR = ContainerCell.CurrentTR.multiply(cell.TRFL)
           cell.level = ContainerCell.level + 1 
           univ,ff = BuildUniverse((cell.FILL,levelMax),cell,AllUniverses,universeCut=universeCut)
           CADUniverse.append( univ )
           fails.extend(ff)

    return ((ContainerCell.name,Ustart),CADUniverse),fails



def makeTree(CADdoc,CADCells):

    label,universeCADCells = CADCells
    groupObj = CADdoc.addObject('App::Part','Materials')
    
    groupObj.Label = 'Universe_{}_Container_{}'.format(label[1],label[0])
    
    CADObj = {}
    for i,c in enumerate(universeCADCells):
        if type(c) is tuple:
           groupObj.addObject(makeTree(CADdoc,c))
        else:    
           featObj = CADdoc.addObject( "Part::FeaturePython", "solid{}".format(i))
           featObj.Label = "Cell_{}_{}".format(c.name,c.MAT)
           featObj.Shape = c.shape
           if c.MAT not in CADObj.keys():
              CADObj[c.MAT] = [featObj]
           else:  
              CADObj[c.MAT].append(featObj)

    for mat,matGroup in CADObj.items():
       groupMatObj = CADdoc.addObject('App::Part','Materials')
       groupMatObj.Label = 'Material_{}'.format(mat)
       groupMatObj.addObjects(matGroup)
       groupObj.addObject(groupMatObj)
    
    return  groupObj  


src/GEOReverse/Modules/buildCAD.py
GEOUNED-code/GEOUNED
import FreeCAD, Part
import BOPTools.SplitAPI
from   BOPTools import GeneralFuseResult as GFR
from GEOReverse.Modules.Utils.booleanFunction import BoolSequence
from GEOReverse.Modules.splitFunction import SplitSolid,splitBase,joinBase
from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids
from GEOReverse.Modules.options import Options

import math


def getPart(slist):
    sol = []
    for s in slist:
       if type(s) is list :
          sol.extend(getPart(s))
       else:
          sol.append(s)
    return sol      

def BuildSolid(cell,boundBox,mode='oneByOne',simplify=False):

    cutCell = cell.makeBox(boundBox) 
    #cell.definition = BoolSequence(cell.definition.str)
    cell.cleanUndefined()

    celParts = BuildDepth(cell,splitBase(cutCell),mode,True,simplify)

    celParts = getPart(celParts)
    #print('celparts',len(celParts))
    shapeParts = []
    for i,s in enumerate(celParts):
        shapeParts.append(s.base)
        #s.base.exportStep('solid{}.stp'.format(i))

 #   tt = FuseSolid(shapeParts)
 #   tt = tt.removeSplitter()
    #tt=Part.makeCompound(shapeParts)
    #tt.exportStep('cell{}.stp'.format(cell.name))
    return shapeParts
    #return FuseSolid(shapeParts)


def BuildDepth(cell,cutShape,mode,baseBox,simplify=False,loop=0):
    
    loop += 1
    seq = cell.definition
    if seq.level == 0 :
       if baseBox :
          cutShape,cut = BuildSolidParts(cell,cutShape,mode)
       else:
          cutShape,cut = BuildSolidParts(cell,cutShape,'solids')
       return cutShape

    if type(cutShape) is not list: cutShape=[cutShape]
    newCutShape = []
    for i,CS in enumerate(cutShape):
       cbaseBox = baseBox 
       #CS.base.exportStep('CS_{}_{}.stp'.format(i,str(cell.definition)))
       #CTable =buildCTableFromSolids(cell.makeBox(CS.base.BoundBox),cell.surfaces,option='full')
       #cell.definition.simplify(CTable)
       cell.definition.groupSingle()

       if type(cell.definition.elements) is not bool :
          if cell.definition.level == 0: 
              tmp = BoolSequence(operator=cell.definition.operator)
              tmp.append(cell.definition)
              cell.definition = tmp

          if seq.operator == 'AND':
             part = CS
             for e in cell.definition.elements:
                part = BuildDepth(cell.getSubCell(e),part,mode,cbaseBox,simplify,loop=loop)
                cbaseBox = False
             newCutShape.extend(part)
          else:
             cellParts = []
             for e in cell.definition.elements:
                sub = cell.getSubCell(e)
                part = BuildDepth(sub,CS,mode,baseBox,simplify,loop=loop)
                cellParts.extend(part)

             JB = joinBase(cellParts)
             if JB.base is not None : newCutShape.append(JB)
         
       elif cell.definition.elements : 
           newCutShape.append(CS)

    cutShape=newCutShape
    return cutShape    



def BuildSolidParts(cell,base,mode):

    # part if several base in input
    if type(base) is list or type(base) is tuple :
       fullPart = []
       cutPart  = []
       for b in base :
          fullList,cutList = BuildSolidParts(cell,b,mode)
          fullPart.extend(fullList)
          cutPart.extend(cutList)
       return fullPart,cutPart
    
    boundBox = base.base.BoundBox
    surfaces = tuple(cell.surfaces.values())
    #print('\nbuild Parts :',mode)
    #print(cell.definition)
    #print(boundBox)

    if mode == 'solids' :
       #boundBox.enlarge(10)
       if cell.definition.operator=='OR' and False :
           Def = cell.definition
           cell.definition = cell.definition.getComplementary()
           cell.buildShape(boundBox,force=False,simplify=False)
           cell.definition = Def
       else:
           cell.buildShape(boundBox,force=True,simplify=False,fuse=True)

       #print('export')
       # base.base.exportStep('base.stp')
       #name=''.join(str(cell.definition))
       #cell.shape.exportStep('sol{}.stp'.format(name))
    else:
       cell.buildSurfaceShape(boundBox)    

    if not surfaces:
        print('not cutting surfaces') 
        return tuple(base.base),tuple()
    if mode == 'solids':
         full, cut  =  SplitSolid(base,surfaces,cell,solidTool=True,tolerance=Options.splitTolerance)
    elif mode == 'allSurfaces':
         full, cut  =  SplitSolid(base,surfaces,cell,tolerance=Options.splitTolerance)

    elif mode == 'planeFirst' :
        planes = []
        others = []
        for s in surfaces: 
           #s.buildShape( boundBox)
           #s.shape.exportStep('Tool_{}_{}.stp'.format(s.type,s.id)) 
           if s.type == 'plane' :
              planes.append(s)
           else:
              others.append(s)

        if planes :
            
           full, cut  = SplitSolid(base,planes,cell,tolerance=Options.splitTolerance)
           #for i,s in enumerate(full):
           #    s.exportStep('fullplane_{}.stp'.format(i))
           #for i,s in enumerate(cut):
           #    s.base.exportStep('cutplane_{}.stp'.format(i))
           #print('planes',full)
           #print('planes',cut)
        else:
           full = [] 
           cut  = base
           
        if others :   
           newf, cut  = SplitSolid(cut,others,cell,tolerance=Options.splitTolerance)
           #print('others',newf)
           #print('others',cut)
        else:
           newf = []
        full.extend(newf)

    elif mode == 'otherFirst' :
        planes = []
        others = []
        for s in surfaces:
           # s.buildShape( boundBox) 
           if s.type == 'plane' :
              planes.append(s)
           else:
              others.append(s)

        if others :
           full, cut  = SplitSolid(base,others,cell,tolerance=Options.splitTolerance)
           #print('others',full)
           #print('others',cut)
        else:
           full = [] 
           cut  = base
           
        if planes :   
           newf, cut  = SplitSolid(cut,planes,cell,tolerance=Options.splitTolerance)
           #print('planes',newf)
           #print('planes',cut)

        else:
           newf = []

        full.extend(newf)
        
    elif mode == 'oneByOne':
        planes = []
        others = []
        for s in surfaces:
           if s.type == 'plane' :
              planes.append(s)
           else:
              others.append(s)
              
        if planes :
           full, cut  = SplitSolid(base,planes,cell,tolerance=Options.splitTolerance)
        else:
           full = [] 
           cut  = base

        #cut[0].base.exportStep('cutPlane.stp')
        for surf in others :
           newf, cut  = SplitSolid(cut,(surf,),cell,tolerance=Options.splitTolerance)
           full.extend(newf)

    elif mode == 'otherOneByOne':
        planes = []
        others = []
        for s in surfaces:
           if s.type == 'plane' :
              planes.append(s)
           else:
              others.append(s)

        cut = base
        full = []
        for surf in others :
           newf, cut  = SplitSolid(cut,(surf,),cell,tolerance=Options.splitTolerance)
           full.extend(newf)

        for surf in planes :   
           newf, cut  = SplitSolid(cut,(surf,),cell,tolerance=Options.splitTolerance)
           full.extend(newf) 
        
    return full,cut
    

def FuseSolid(parts):
    if (len(parts)) <= 1:
       if parts : 
          solid = parts[0]
       else:
          return None 
    else:
       try:
          fused = parts[0].fuse(parts[1:])
       except:
          fused = None

       if fused is not None:
         try : 
             refinedfused = fused.removeSplitter()
         except :
             refinedfused = fused
           
         if refinedfused.isValid() :
             solid = refinedfused
         else :
             if fused.isValid():
                solid = fused
             else:
                solid = Part.makeCompound(parts)
       else:
         solid = Part.makeCompound(parts)
           
    if solid.Volume < 0 : solid.reverse()
    return solid


def noOR(Seq):
  if len(Seq.elements) == 1 : 
    #Seq.operator = 'AND'
    return Seq
  newOR=BoolSequence(operator='OR')
  neg = []
  for e in Seq.elements:
     AND = BoolSequence(operator='AND')
     AND.append(*neg,e)
     newOR.append(AND)
     neg.append(-e)
  return newOR


src/GEOReverse/Modules/buildSolidCell.py
GEOUNED-code/GEOUNED
class Options :
   splitTolerance = 1.e-2


src/GEOReverse/Modules/options.py
GEOUNED-code/GEOUNED
import configparser
from GEOReverse.Modules.options import Options


def setOptions(optionFile):

   setting = {'fileIn'   : ''               ,
              'fileOut'  : ''               ,
              'outBox'   : tuple()          ,

              'UStart'   : 0                ,
              'levelMax' : 'all'            ,
              'cell'     : ('all',)         ,
              'mat'      : ('exclude',(0,)) }


   sectionNames =('Setting', 'Levels', 'Cells', 'Materials','Options')

   config = configparser.ConfigParser()
   config.optionxform = str
   config.read(optionFile)


   fileData = False
   for section in config.sections():
       if section not in sectionNames:
            print('{} bad section name'.format(section))
            exit()

       if section == 'Setting' :
           fileIn,fileOut,outBox = getSetting(config)

           i = 0
           if fileIn  is not None : 
              setting['fileIn']  = fileIn
              i += 1

           if fileOut is not None : 
              setting['fileOut'] = fileOut
              i += 1

           if outBox  is not None : 
              setting['outBox']  = outBox
              i += 1

           if i==3 : fileData = True


       elif section == 'Levels' :
           UStart,levMax = getLevel(config)
           if UStart is not None: setting['UStart'] = UStart
           if levMax is not None: setting['levelMax'] = levMax


       elif section in ('Cells','Materials'): 
           CMRange = getRange(section,config)
           if CMRange is None : continue
           if section == 'Cells':
              setting['cell'] = CMRange
           else:
              setting['mat'] = CMRange

       elif section == 'Options' :
           setSecOptions(config)

   if not fileData:
       print('missing input data in [Setting] section')
       exit()


   return setting


def getSetting(config):

   fileIn, fileOut, outBox = None, None, None
   for key in config['Setting'].keys() :

      if  key == 'mcnpFile':
         fileIn  =  config.get('Setting',key)
      elif key == 'CADFile':
         fileOut =  config.get('Setting',key)
      elif key == 'outBox':
         outBox  = getBoxData(config.get('Setting',key))
      else:
          print('{} bad keyword. Ignored'.format(key))       

   return fileIn, fileOut, outBox 

      
def getLevel(config):

   UStart, levMax = None, None
   for key in config['Levels'].keys() :
      if  key == 'levelMax':
         levMax  =  config.get('Levels',key)
         if levMax.lower() == 'all' : levMax = 'all'
         elif levMax.isnumeric()    : levMax = int(UStart)
         else:
            print('bad value for levMax')
      elif key == 'UStart':
         UStart =  config.getint('Levels',key)
      else:
          print('{} bad keyword. Ignored'.format(key))       

   return UStart, levMax 


def getRange(section,config):
   rType   = None
   rawRange = None
   for key in config[section].keys() :
      if  key == 'rangeType':
         rType  =  config.get(section,key)
      elif key == 'range':
         rawRange  =  config.get(section,key)
      else:
          print('{} bad keyword. Ignored'.format(key))       

   
   if rType is None :
       return None
   elif rType.lower() == 'all':
       return ('all',)

   elif rType.lower() == 'exclude' or rType.lower() == 'include':
       ctRange = getRangeData(rawRange)
       if ctRange is None :
          print('bad range in section {}'.format(section))
          return None 
       else:
          return (rType.lower(),ctRange)
   else:
       return None     


def getBoxData(string):
   data = tuple(map(float,string.split()))
   if len(data) != 6 :
      print('bad Outbox value number') 
      exit()

   elif( (data[0] > data[1]) or
         (data[2] > data[3]) or
         (data[4] > data[5]) )   :
      print('bad Outbox boundaries') 
      exit()

   else:
      return (10*data[0],10*data[2],10*data[4],10*data[1],10*data[3],10*data[5])

 
def getRangeData(rawData):
   if rawData is None : return None

   intervals = rawData.split(',')
   rangeValues = set()

   for i in intervals:
      if i.strip().isnumeric() :
         rangeValues.add(int(i))
      else:
         bounds = i.split(':')
         if len(bounds) != 2:
            print('bad range definition')
            return None
         else:
            if bounds[0].strip().isnumeric() and bounds[1].strip().isnumeric():
               i1,i2 = map(int,bounds)
               if i1 > i2 :
                  print('bad range definition')
                  return None
               else:
                  rangeValues.update( range(i1,i2+1) ) 
            else:
               print('bad range definition')
               return None

   rangeValues = list(rangeValues)
   rangeValues.sort()
   return tuple(rangeValues)

def setSecOptions(config):

   for key in config['Options'].keys() :
      if  key == 'splitTolerance':
         tolerance  =  config.getfloat('Options',key)
         setattr(Options,key ,tolerance)
      else:
         print('{} bad keyword. Ignored'.format(key))       

   return  


def rangeGenerator(intervals):
   for i in intervals:
      if type(i) is tuple :
         for v in range(i[0],i[1]+1):
             yield v
      else:
         yield i





src/GEOReverse/Modules/processInp.py
GEOUNED-code/GEOUNED
import sys
import re
import math
   


#########################################
# define patterns to be found in string #
#########################################


# used in card_split
celmat=re.compile(r"(?P<cel>^ *\d+) +(?P<scnd>(\d+|like))",re.I)      # identify first two entries on cell card (cell name, material)
grlnumber=re.compile(r"[-+]?(\d+\.\d+|\.\d+|\d+\.?)(e[+-]\d+)?",re.I) # identify a general number form signed integer, float or exponential
param=re.compile(r"((^|\n {5})[\(\):\-\+\d+\.\# ]*)([\*a-z])",re.I)   # identity begining of the paramter part of the cell card
likebut=re.compile(r"but",re.I)                                       # identify likebut  card
trans=re.compile(r"trcl|fill= *\d+[ c\$\n]*\(,",re.I)                 # identify tranformed card
unsignedint = re.compile(r"\d+")

# user in get_stat function
reword=re.compile(r"(\d+|\(|\)|:|\#)")                    # word identification in cell line
compcell=re.compile(r"\#\d")                              # identify hashcell complement operator


# used in Complementary operator function
number=re.compile(r"(?P<number>[-+]?\d+)")                                     # signed (+-) (or not) numbers
# leftp=re.compile(r"^ *(?P<left>[-\d\(\#])",re.M)                               # identify first valid character
leftp=re.compile(r"(?P<left>[-+\d\(\#])",re.I)                                  # identify first valid character
rightp=re.compile(r"(?P<right>[ c\$\n]*$)",re.I)                               # identify last valid character
#interblk=re.compile(r"(?P<previous>\d)(?P<next>(( +| *(\$)?\n(C\n)* *)[-+]?\d))") # two numbers separated by blank (or newline or comments)
#intercls=re.compile(r"(?P<previous>\))(?P<next>(( *| *(\$)?\n(C\n)* *)[-+]?\d))") # closed parenthesis followed by number
#interopn=re.compile(r"(?P<previous>\d)(?P<next>(( *| *(\$)?\n(C\n)* *)\())")   # number followed by opened parenthesis
#intercop=re.compile(r"(?P<previous>\))(?P<next>(( *| *(\$)?\n(C\n)* *)\())")   # closed parenthesis followed by opened parenthesis
interblk=re.compile(r"(?P<previous>\d)(?P<next>(( +| *((\n *)?\$|\nC)*\n *)[-+]?\d))") # two numbers separated by blank (or newline or comments)
intercls=re.compile(r"(?P<previous>\))(?P<next>(( *| *((\n *)?\$|\nC)*\n *)[-+]?\d))") # closed parenthesis followed by number
interopn=re.compile(r"(?P<previous>\d)(?P<next>(( *| *((\n *)?\$|\nC)*\n *)\())")   # number followed by opened parenthesis
intercop=re.compile(r"(?P<previous>\))(?P<next>(( *| *((\n *)?\$|\nC)*\n *)\())")   # closed parenthesis followed by opened parenthesis
colonamp=re.compile(r"[:&]")                                                   # colon or amperserand

# used for remove redundant parenthesis function
mostinner=re.compile(r"\([^\(^\)]*\)")                                      # identify most inner parentheses
bracketsemi=re.compile(r"[\]\[;]")                                          # square bracket or semicolon
blnkline=re.compile(r"^ *\n",re.M)                                          # identify blank line
contline=re.compile(r"\n {0,4}(?P<start>[^c^ ])",re.I)                      # identify character other than 'C' in fisrt 5 columns
comdollar=re.compile(r"\n(?P<blnk> *)\$")                                   # identify dollar on 'blank line'
startgeom=re.compile(r"(?P<previous>^ *)(?P<start>[\-\+\d])")               # identify beginning of the geomtric part
endgeom=re.compile(r"(?P<last>\d)(?P<next> *((\n *)?\$|\nc)?(\n *)?$)",re.I)    # identify end of the geomtric part
#endgeom=re.compile(r"(?P<last>\d)(?P<next> *(\$|\nc)?(\n *)?$)",re.I)                      # identify end of the geomtric part

# other
rehash=re.compile(r"# *(\d+|\()")                                             # find beginning of complementary operator (both cell and surf)
parent=re.compile(r"[\(|\)]")                                               # position of open and close parenthesis (get_hashcell)
gline=re.compile(r"(^ ?[\(\):\-\+\d+\.\# ]+|\n {5}[\(\):\-\+\d+\.\# ]+)",re.I)  # valid geometric part of the line       (remove/restore_comments)
comments=re.compile(r"((\n *)?\$|\n *c)",re.I)                               # begining of comment part               (remove/restore_comments)
#comments=re.compile(r"\$|\n *c",re.I)                               # begining of comment part               (remove/restore_comments)
celtrsf = re.compile(r"TRCL *= *",re.I)
celuniverse = re.compile(r"U *= *",re.I)
celfill = re.compile(r"FILL *= *",re.I)
trfnumber=re.compile(r"([-+]?(\d+\.\d+|\.\d+|\d+\.?)(e[+-]\d+)?)|\)",re.I)  # search for general number or close bracket )
likemat=re.compile(r"MAT *= *(?P<mat>\d+)",re.I)      # identify material value on like but card
dollar = re.compile(r"\$.*\n",re.I)
############################################################
# Auxiliary functions used in regular expresion functions  #
############################################################
def remove_dollar(string):
   m = dollar.search(string)
   while m:
     string = string[0:m.start()] + ' ' + string[m.end():]
     m = dollar.search(string)
   return string
   
    
def redundant(m,geom):
   """ check if the inner parentheses are redundant """
   term = m.group()

   # Find first valid character at the left of the  parenthese
   hashsmb = False
   leftOK= True
   left = m.start()-1
   while left > -1:
       if geom[left] in ('\n','C','$',' '):
          left -= 1
       else:
          if geom[left] not in ('(',':') : leftOK  = False
          if (geom[left] == '#') : hashsmb  = True
          break

  # if hash symbol found means parentheses delimits complementary
  # cell defined with surface. Theses parentheses are not redundants
   if hashsmb : return False

  # check if no ':' (or) are inside the parenthese
  # if not, parentheses are redundants
   if (term.find(':') == -1) : return True

  # Find first valid character at the right of the  parenthese
   rightOK= True
   right = m.end()
   while right < len(geom)  :
       if geom[right] in ('\n','C','$',' '):
          right += 1
       else:
          if geom[right] not in (')',':') : rightOK  = False
          break

  # if parentheses are like:
  # {( or : } ( ....... ) {) or :}
  # parentheses are redundants

   if leftOK and rightOK :
       return True
   else:
       return False

# function used in Regular expresion sub function
# function user in complementary function
# change the sign of the number
def chgsign(m):
    num=m.group(0)
    if num[0] == '-':
      return num[1:]
    if num[0] == '+':
      return '-'+num[1:]
    else:
      return '-'+num

# function used in Regular expersion sub function
# function user in complementary function
# change ':' in ')('  and
#        '&' in ':'
def repl_inter_union(m):
    if m.group(0) == ':' :
       return ')('
    else :
       return ':'

# function used in Regular expersion sub function
# function user in remove_redundant function
# restore curve parentheses and colon characters
def reverse_repl(m):
    symb=m.group(0)
    if symb == '[' :
      return '('
    elif symb == ']' :
      return ')'
    else :
      return ':'
############################################################

def complementary(ccell,outter=True) :
    """ return the complementary cell """
    wrkcell = cline(ccell.str)
    if (wrkcell.str[-1] == '\n') : wrkcell.str=wrkcell.str[:-1]

    # simplify comment in geometry string
    wrkcell.remove_comments()

    # insert external parenthesis
    wrkcell.str= re.sub(leftp,r"(\g<left>",wrkcell.str,count=1)
    wrkcell.str= re.sub(rightp,r")\g<right>",wrkcell.str,count=1)

    # insert '&' as intersection operator
    wrkcell.str=re.sub(interblk,r"\g<previous>&\g<next>",wrkcell.str)  # change intersection separate by blank space ie: "number number"
    wrkcell.str=re.sub(interblk,r"\g<previous>&\g<next>",wrkcell.str)  # 2nd pass intersection blank space (require 2 pass)
    wrkcell.str=re.sub(intercls,r"\g<previous>&\g<next>",wrkcell.str)  # change intersection close parenthesis ie: ") number"
    wrkcell.str=re.sub(interopn,r"\g<previous>&\g<next>",wrkcell.str)  # change intersection open  parenthesis ie: "number ("
    wrkcell.str=re.sub(intercop,r"\g<previous>&\g<next>",wrkcell.str)  # change intersection close-open  parenthesis ie: ") ("

    # invert operators
    # substitute colon by ')(' and  '&' by colon
    wrkcell.str=re.sub(colonamp,repl_inter_union,wrkcell.str)
    wrkcell.remove_redundant(remove_com=False)

    # Change signs
    wrkcell.str=re.sub(number,chgsign,wrkcell.str)

    # insert external parenthesis
    if outter :
      wrkcell.str= re.sub(leftp,r"(\g<left>",wrkcell.str,count=1)
      wrkcell.str= re.sub(rightp,r")\g<right>",wrkcell.str,count=1)

# restore original comments
    wrkcell.restore_comments()
    return wrkcell.str

############################################################
class cline():
   def __init__(self,line):
     self.str=line

   def copy(self):
      return cline(self.str)

   def getSurfacesNumbers(self):  
       s = set(unsignedint.findall(self.str))
       return tuple(map(int,s))
      
   def remove_multispace(self):
      self.str.strip()
      self.str = re.sub(r' +',' ',self.str)

   def remove_cr(self):
      self.str=re.sub(r'\n',' ',self.str)
      return
      
   def remove_comments(self,full=False):
      """ Remove the text of the comment. The symbol 'C' or '$' is
kept in the line if 'full' option is False (default)"""
      celltab = re.split(gline,self.str)
      cont=True
      while cont:
         try:
            celltab.remove('')
         except:
            cont=False

      self.__comtab__=[]
      for i,s in enumerate(celltab) :
         c = comments.match(s)
         if ( c ):
            if not full :
              self.__comtab__.append(s)
              celltab[i] = c.group()
            else:
              celltab[i] = ''

      self.str=''.join(celltab)
      return

   def restore_comments(self):
      """ Restore the text of the comment."""
      celltab = re.split(gline,self.str)
      cont=True
      while cont:
         try:
            celltab.remove('')
         except:
            cont=False

      j = 0
      for i,s in enumerate(celltab) :
          c = comments.match(s)
          if ( c ):
             celltab[i] = self.__comtab__[j]
             j += 1

      self.str = ''.join(celltab)
      return

   def outterTerms(self):
      cgeom = cline(self.str)

      cgeom.remove_comments(full=True)
      cgeom.remove_redundant()
      cgeom.remove_cr()
      geom = cgeom.str

      # Loop until no redundant parentheses are found
      cont = True
      
      while cont:
        # Loop over most inner parentheses
        pos = 0
        cont = False
        while True :
          m = mostinner.search(geom,pos)
          if not m : break
          cont = True
          if redundant(m,geom):
             # remove redundant parentheses
             geom = geom[:m.start()]+ ' ' + geom[m.start()+1:m.end()-1]+ ' ' + geom[m.end():]
          else:
             # replace no redundant parentheses by 0 and : by ;
             zeros = '0'* (m.end()-m.start())
             geom = geom[:m.start()] + zeros + geom[m.end():]

          pos = m.end()

      if ':' in geom :
          terms = []
          pos = 0
          while True :
              newpos = geom.find(':',pos)
              if newpos == -1 :
                  terms.append(cgeom.str[pos:].strip())
                  break
              terms.append(cgeom.str[pos:newpos].strip())
              pos = newpos + 1                       
          return (terms,'OR')
      else:
          terms = []
          pos = 0
          while True:
              m = number.search(geom,pos)
              if not m : break
              terms.append(cgeom.str[m.start():m.end()])
              pos = m.end()                          
          return (terms,'AND')

   def remove_redundant(self,remove_com=True,remopt='nochg'):
      """ return cell without redundant parenthesis """

      #simplify comment in geometry string
      if remove_com: self.remove_comments()
      geom = self.str

      if (remopt == 'nochg' and geom.find(')') == -1 ) :
          self.removedp = None
          return

      porg=self.countP()
      # Loop until no redundant parentheses are found
      cont = True
      while cont:
        # Loop over most inner parentheses
        pos = 0
        cont = False
        while True :
          m = mostinner.search(geom,pos)
          if not m : break
          cont = True
          if redundant(m,geom):
             # remove redundant parentheses
             geom = geom[:m.start()]+ ' ' + geom[m.start()+1:m.end()-1]+ ' ' + geom[m.end():]
          else:
             # replace no redundant parentheses by [] and : by ;
             term = geom[m.start()+1:m.end()-1].replace(':',';')
             geom = geom[:m.start()] + '[' + term + ']' + geom[m.end():]
          pos = m.end()

      # restore curved parenthesis and colon
      geom=re.sub(bracketsemi,reverse_repl,geom)

      # remove possible blank line
      geom=re.sub(blnkline,'',geom)

      # ensure 5 blanks continous line
      geom=re.sub(contline, r'\n     \g<start>',geom)

      if remopt != 'all' :
        # add parenthesis to set geom as MCNP complex cell
        if geom.find(':') == -1 and geom.find('#') == -1 :
           geom=re.sub(startgeom,r'\g<previous>(\g<start>',geom)
           geom=re.sub(endgeom,r'\g<last>)\g<next>',geom)

      # restore original comments
      self.str = geom
      pmod=self.countP()
      if remove_com: self.restore_comments()

      # subtitute comment $ with  blank line
      self.str=re.sub(comdollar,r'\nC\g<blnk>',self.str)
      pdiff = [x-y for x,y in zip(pmod,porg)]
      self.removedp=pdiff
      return


   def get_hashcell(self,start=0):
      """ get the complementary cell defined with surfaces combination """
      count=0
      for p in  parent.finditer(self.str,start):
         if (p.group() == '(') :
           count += 1
         else:
           count -= 1
         if (count == 0 ):
           end=p.end()
           cell=cline(self.str[start+1:end])
           break
      return cell,end

   def replace(self,surf,new,pos = 0):
      
      s1 = str(surf)
      s2 = str(new)
      m = unsignedint.search(self.str,pos)
      if not m :
         print('no number in line')
         return -1
      if surf == new :
         return m.start()+len(s2)
      elif m.group() == s1 :
         self.str = self.str[0:m.start()] + s2 + self.str[m.end():]
         return m.start()+len(s2)
      else:
         print('number {} not found'.format(surf))
         return -1
      
   
   def countP(self):
      lp=self.str.count('(')
      rp=self.str.count(')')
      return (lp,rp)

   def SplitCell(self):
       self.remove_redundant(remopt='all')
       self.remove_comments()
       geom = self.str
       self.restore_comments()

    # insert '&' as intersection operator
       geom=re.sub(interblk,r"\g<previous>&\g<next>",geom)  # change intersection separate by blank space ie: "number number"
       geom=re.sub(interblk,r"\g<previous>&\g<next>",geom)  # 2nd pass intersection blank space (require 2 pass)
       geom=re.sub(intercls,r"\g<previous>&\g<next>",geom)  # change intersection close parenthesis ie: ") number"
       geom=re.sub(interopn,r"\g<previous>&\g<next>",geom)  # change intersection open  parenthesis ie: "number ("
       geom=re.sub(intercop,r"\g<previous>&\g<next>",geom)  # change intersection close-open  parenthesis ie: ") ("


       parts=[]
       block = ''
       iopen = 0
       ctype = None

       for c in geom :
         if c == '\n': continue
         if c in [':','&'] :
           if iopen == 0 :
             if ctype == None :
               if c == '&' : ctype ='AND'
               if c == ':' : ctype ='OR'
             if ctype == 'AND' and c==':' :
                ctype='OR'
                break
         elif c == '(' :
           iopen += 1
         elif c == ')' :
           iopen -= 1

       for c in geom:
         if c == '\n': continue
         if c in [':','&'] :
           if iopen == 0 :
             if ctype == 'AND' and c == '&' :
                parts.append(block)
                block = ''
             if ctype == 'OR' and  c == ':' :
                parts.append(block)
                block = ''
           else:
             if c != '&' : 
               block = block + c
         else :
           if c != '&' : 
             block = block + c 
           if c == '(' :
             iopen += 1
           elif c == ')' :
             iopen -= 1
       parts.append(block)
       return parts,ctype  
      
       
############################################################
class cell_card_string():

   def __init__(self,card):
      self.stat={ 'word'     : None  ,\
                  'hashcell' : None  ,\
                  'hashsurf' : None  ,\
                  'hash'     : None   }

      self.name = None
      self.MAT  = None
      self.U    = None
      self.FILL = None
      self.TR   = None
      self.TRCL = None
      self.__card_split__(card)
      self.__get_data__()      

   def __card_split__(self,cardin):
      """ Split the card string in three parts :
              - headstr : string containing the cell name, mat number and density (if mat != 0) of the cell
              - geom    : cline class containing the part of the geometric definition of the cell
              - param    : cline class containing the cell parameters part
           hproc is true if the complementary operator of the cell can be substituted"""

      m=celmat.match(cardin)
      self.name = int(m.group('cel'))
      self.hproc=True
      self.likeCell = None
      if m.group('scnd').lower() == 'like':
         self.headstr    =       cardin[:m.start('scnd')]
         s = likebut.search(cardin,m.end('scnd'))
         self.geom = cline(cardin[m.start('scnd'):s.end()])
         self.parm = cline(cardin[s.end():])
         self.hproc=False
         mc = unsignedint.search(self.geom.str)
         self.likeCell = int(mc.group())
         ml = likemat.search(self.parm.str)
         if ml :
            self.MAT = int(m1.group('mat'))
      elif m.group('scnd') == '0':
         cstart=m.end('scnd')
         self.MAT = 0
      else :
         self.MAT = int(m.group('scnd'))
         p=grlnumber.search(cardin,m.end('scnd'))
         cstart=p.end()

      if self.hproc:
         self.headstr = cardin[:cstart]
         cellcard=cline(cardin[cstart:])
         cellcard.remove_comments()
         m = param.search(cellcard.str)
         if m:
            linecut = cellcard.str[:m.end(1)].count('\n')
         else:
            linecut = cellcard.str.count('\n')

         cellcard.restore_comments()

         # look for the last line geometry string
         if linecut != 0 :
            pos=0
            c=0
            while c != linecut :
               pos=cellcard.str.find('\n',pos)
               c += 1
            m = param.search(cellcard.str,pos)

         if m:
            start = m.end(1)
            self.geom = cline(cellcard.str[:start])
            self.parm = cline(cellcard.str[start:])

            # look for transformation in cell parameters
            self.parm.remove_comments()
            m = trans.search(self.parm.str)
            if m : self.hproc = False
            self.parm.restore_comments()
         else :
            self.geom = cline(cellcard.str)
            self.parm = cline('')

      return

   def __get_data__(self):
             

      mt = celtrsf.search(self.parm.str)
      if mt :
         string = self.parm.str[mt.end():]
         m = grlnumber.search(string)
         
         if self.parm.str[mt.start()-1] == '*' :
           angle = True
         else:                
           angle = False              
         
         if '(' in  string[:m.end()] :
            self.TRCL=[]         
            pos = 0                          
            while True:
               m = trfnumber.search(string,pos)
               pos = m.end()
               if not m or m.group() == ')' : break
               self.TRCL.append(float(m.group()))
            if angle and len(self.TRCL) > 3 :
               self.TRCL[3:12] = list(map(math.radians,self.TRCL[3:12] ))
               self.TRCL[3:12] = list(map(math.cos,self.TRCL[3:12]))          
         else:            
            self.TRCL = int(m.group())


      # get universe number
      m = celuniverse.search(self.parm.str)
      if m:
        m = grlnumber.search(self.parm.str[m.end():])
        self.U = int(m.group())      

      # get Fill number
      mf = celfill.search(self.parm.str)
      if mf :
        fillstring = remove_dollar(self.parm.str[mf.end():]) 
        m  = grlnumber.search(fillstring)
        self.FILL = int(m.group())

        # look for transformation after fill card
        m = re.search(r" *\(",fillstring)
        if m :
          if self.parm.str[mf.start()-1] == '*' :
             angle = True
          else:                
             angle = False

          fillstring = fillstring[m.end():]  
          m = re.search(r"\)",fillstring)
          string = fillstring[:m.start()]
          tr = string.split()
          if len(tr) == 1 :
              self.TR = int(tr[0])
          else:
              self.TR = list(map(float,tr))
              if angle and len(self.TR) > 3 :
                    self.TR[3:12] = list(map(math.radians,self.TR[3:12] ))
                    self.TR[3:12] = list(map(math.cos,self.TR[3:12]))                       

       
   def get_stat(self,remove_com=True):
      """ Count and return the number of words and hashes on the line."""

      if remove_com : self.geom.remove_comments()

      words    = len(reword.findall(self.geom.str))
      hashcell = len(compcell.findall(self.geom.str))
      hashtot  = self.geom.str.count('#')

      self.stat={ 'words'   : words,
                  'hash'    : hashtot,
                  'hascell' : hashcell,
                  'hashsur' : hashtot-hashcell }
      if remove_com : self.geom.restore_comments()
      return [words, hashtot, hashcell, hashtot-hashcell]

   def get_lines(self):
      """ split string card in the format Cards.lines of Cards object"""
      card=self.headstr + self.geom.str + self.parm.str

      # remove blank line introduced during the process
      card=re.sub(blnkline,'',card)

      # subtitute comment $ with  blank line
      card=re.sub(comdollar, r'\nC\g<blnk>',card)

      # ensure 5 blanks continous line
      card=re.sub(contline, r'\n     \g<start>',card)

      if (card[-1] == '\n') : card=card[:-1]
      return  list(map(lambda x: x+'\n' ,card.split('\n')))


def remove_hash(cards,cname,keepComments=True):
   def remove(card,cname,keepComments):
       """ remove complementary operator and subtitute by complementary cell """
       if "parser.Card" in str(type(card)):
          celline=''.join(card.lines)
          cardstr = cell_card_string(celline)
       else:
          cardstr = card
       cardstr.get_stat()
       cardstr.geom.remove_comments(full=not keepComments)
       if (not cardstr.hproc) or (cardstr.stat['hash'] == 0) :return cardstr.geom  # no complementary operator or cannot be # cannot be removed
       cell=cline(cardstr.geom.str)
       # find all operators in the cell and
       # substitute all complementary operators
       # locate object in list to reverse iteration
       hashgroup=[]
       start = 0

       lencel = len(cell.str)
       while True:
          ic   = cell.str.lower().find('c',start)
          idol = cell.str.find('$',start)
          if idol < 0 : idol = lencel
          if ic   < 0 : ic   = lencel
          end = min(idol, ic)
          for m in (rehash.finditer(cell.str,start,end)):
             hashgroup.append(m)
          start = cell.str.find('\n',end)
          if (end  == lencel) : break

       for m in reversed(hashgroup):
          start=m.start()
          if m.group(1) == '(':                       # complementary cell defined as surface intersections
             hcell,end=cell.get_hashcell(start)
             cellmod=cell.str[0:start] + complementary(hcell) + cell.str[end:]
             cell = cline(cellmod)
          else:
             hcname=int(m.group(1))                  # complementary cell defined with other cell index
             newdef = remove(cards[hcname],hcname,keepComments)            # remove complementary operator in new cell if necessary
             end=m.end()
             cellmod=cell.str[0:start]                                     \
                 + '      '+complementary(newdef)+ ' '                      \
                 + cell.str[end:]
             cell = cline(cellmod)
       return cell

   newcell = remove(cards[cname],cname,keepComments)
   return cline(newcell.str)



src/GEOReverse/Modules/remh.py
GEOUNED-code/GEOUNED
import FreeCAD, Part
import BOPTools.SplitAPI
from GEOReverse.Modules.Utils.booleanFunction import outterTerms
import math


class splitBase:
    def __init__(self,base,knownSurf={}):
        self.base = base
        self.knownSurf = knownSurf
    
def joinBase(baseList):
    shape = []
    surf = {}
    removedKeys = []
    for b in baseList:
       if b.base is not None: shape.append(b.base)
       for k,v in b.knownSurf.items():
           if k in removedKeys : continue
           if k not in surf.keys():
               surf[k] = v
           else:
               if surf[k] == v : 
                  continue
               else:
                  surf[k] = None
                  removedKeys.append(k)

    newbase = FuseSolid(shape)
    return splitBase(newbase,surf)  
     

       

def SplitSolid(base,surfacesCut,cellObj,solidTool=False,tolerance=0.01): #1e-2
    # split Base (shape Object or list/tuple of shapes)
    # with selected surfaces (list of surfaces objects) cutting the base(s) (surfacesCut)
    # cellObj is the CAD object of the working cell to reconstruction.
    # the function return a list of solids enclosed fully in the cell (fullPart)
    # and a list of solids not fully enclosed in the cell (cutPart). These lasts
    # will require more splitting with the others surfaces defining the cell.
   
    fullPart = []
    cutPart  = []

    # part if several base in input
    if type(base) is list or type(base) is tuple :
       for b in base :
          fullList,cutList = SplitSolid(b,surfacesCut,cellObj,tolerance=tolerance)
          fullPart.extend(fullList)
          cutPart.extend(cutList)
       return fullPart,cutPart

     
    # part if base is shape object
    
    if solidTool: 
      Tools = (cellObj.shape,)
    else:
      Tools = tuple( s.shape for s in surfacesCut )
    #for s in surfacesCut:
    #    print(s.type,s.params,s.id)
    #    s.shape.exportStep('tool{}.stp'.format(s.id))
    #base.base.exportStep('base.stp')    
    Solids = BOPTools.SplitAPI.slice(base.base,Tools, 'Split',tolerance = tolerance).Solids
    if not Solids : Solids = [base.base]
    partPositions, partSolids  = space_decomposition(Solids,surfacesCut)

    ii = 0
    for pos,sol in zip(partPositions,partSolids):
        #fullPos = updateSurfacesValues(pos,cellObj.surfaces,base.knownSurf)
        #inSolid = cellObj.definition.evaluate(fullPos)


        if not solidTool : pos.update(base.knownSurf)
        inSolid = cellObj.definition.evaluate(pos)

        #if solidTool :
        #  ii += 1
        #  print(solidTool)
        #  print(cellObj.definition)
        #  print(pos)
        #  print('eval',inSolid)
        #  name = str(cellObj.definition)
        #  sol.exportStep('solid_{}{}.stp'.format(name,ii))
        
        if inSolid :
           fullPart.append(splitBase(sol,pos))
        elif inSolid is None :
           cutPart.append(splitBase(sol,pos))
    return fullPart,cutPart


def updateSurfacesValues (position,surfaces,knownSurf) :
    position.update(knownSurf) 
    sname = set(surfaces.keys())
    pname = set(position.keys())

    fullpos = position.copy()
    for name in sname.difference(pname) :
        fullpos[name] = None
    return fullpos    

# Get the position of subregion with respect
# all cutting surfaces    
def space_decomposition(solids,surfaces):

      component = []
      good_solids = []
      for c in solids:
         if c.Volume < 1e-3 :
            if abs(c.Volume) < 1e-3 :
               continue
            else:
                c.reverse()
                print('Negative solid Volume',c.Volume)
         Svalues = {}
         point = point_inside(c)
         if point == None : 
            continue  # point not found in solid (solid is surface or very thin can be source of lost particules in MCNP)
         for surf in surfaces :
             Svalues[surf.id] = surface_side(point,surf)   
            
         component.append(Svalues)
         good_solids.append(c)
      return component,good_solids

# find one point inside a solid (region)
def point_inside(solid):

      cut_line = 32
      cut_box  = 4
   
      # no poner boundbox, el punto puente caer en una superficie para geometria triangular
      point = solid.CenterOfMass
      points = point
      if solid.isInside(point,0.0,False) : return point

      v1=solid.Vertexes[0].Point
      for vi in range(len(solid.Vertexes)-1,0,-1):
        v2=solid.Vertexes[vi].Point
        dv= (v2-v1)*0.5
      
        n=1
        while True:
           for i in range(n): 
              point = v1 + dv *(1 + 0.5*i)
              if solid.isInside(point,0.0,False) : return point
           n=n*2
           dv=dv*0.5
           if (n > cut_line ) :
             break  

#      Box_Volume = BBox.XLength*BBox.YLength*BBox.ZLength
#      if (solid.Volume < Box_Volume/ math.pow(16,nmax_cut)) :
#           print('very small Solid Volume (solid volume, box volume): {},{}'.format(solid.Volume,Box_Volume))
#           return None        
      BBox = solid.optimalBoundingBox(False)
      box = [BBox.XMin,BBox.XMax,
             BBox.YMin,BBox.YMax,
             BBox.ZMin,BBox.ZMax ]
      
      boxes,centers = CutBox(box)
      n = 0

      while True :
         for p in centers :
            pp = FreeCAD.Vector(p[0],p[1],p[2])
            if solid.isInside(pp,0.0,False) : return pp

         subbox = []
         centers = []
         for b in boxes :
            btab,ctab = CutBox(b)   
            subbox.extend(btab)
            centers.extend(ctab)
         boxes = subbox
         n = n + 1

         if (n == cut_box ) :
           print('Solid not found in bounding Box (Volume : {})'.format(solid.Volume))
           print('Valid Solid : ', solid.isValid())
           return None

# divide a box into 8 smaller boxes
def CutBox(Box):
     xmid = (Box[1]+Box[0])*0.5
     ymid = (Box[3]+Box[2])*0.5
     zmid = (Box[5]+Box[4])*0.5

   
     b1 = (Box[0],xmid,Box[2],ymid,Box[4],zmid)
     p1 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b2 = (xmid,Box[1],Box[2],ymid,Box[4],zmid)
     p2 = (0.5*(xmid+Box[1]), 0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b3 = (Box[0],xmid,ymid,Box[3],Box[4],zmid)
     p3 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))

     b4 = (xmid,Box[1],ymid,Box[3],Box[4],zmid)
     p4 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))
      
     b5 = (Box[0],xmid,Box[2],ymid,zmid,Box[5])
     p5 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b6 = (xmid,Box[1],Box[2],ymid,zmid,Box[5])
     p6 = (0.5*(xmid+Box[1]),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b7 = (Box[0],xmid,ymid,Box[3],zmid,Box[5])
     p7 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))
     
     b8 = (xmid,Box[1],ymid,Box[3],zmid,Box[5])
     p8 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))

     return [b1,b2,b3,b4,b5,b6,b7,b8],[p1,p2,p3,p4,p5,p6,p7,p8]


# check the position of the point with respect
# a surface
def surface_side(p,surf):
     if surf.type == 'sphere' :
        org,R = surf.params
        D = p-org
        inout = D.Length - R

     elif surf.type == 'plane' :
        normal,d = surf.params
        inout = p.dot(normal) - d

     elif surf.type == 'cylinder':
         P,v,R = surf.params
         D = p-P
         inout = D.cross(v).Length - R
         
     elif surf.type == 'cone':
         P,v,t,dblsht = surf.params
         X = p-P
         X.normalize()
         dprod = X.dot(v)
         dprod = max(-1,min(1,dprod))
         a = math.acos(dprod)
         if dblsht : a = math.acos(abs(dprod))  
         inout = a - math.atan(t)
         
     elif surf.type == 'torus':
         P,v,Ra,R = surf.params

         d = p-P
         if v[0] == 1.0 :
            sx1 = d.x*d.x
            sx2 = d.y*d.y
            sx3 = d.z*d.z 
         elif v[1] == 1.0 :
            sx1 = d.y*d.y
            sx2 = d.z*d.z
            sx3 = d.x*d.x
         else :
            sx1 = d.z*d.z
            sx2 = d.x*d.x
            sx3 = d.y*d.y

         inout = sx1/(R*R) + (math.sqrt(sx2+sx3)-Ra)**2/(R*R) -1 
        
     else:
       print ('surface type {} not considered'.format(surf[0]))
       return 
         
     return inout > 0 


#************************************************    

def FuseSolid(parts):
    if (len(parts)) <= 1:
       if parts :
          solid = parts[0]
       else:
          return None
    else:
       try:
          fused = parts[0].fuse(parts[1:])
       except:
          fused = None

       if fused is not None:
         try :
             refinedfused = fused.removeSplitter()
         except :
             refinedfused = fused

         if refinedfused.isValid() :
             solid = refinedfused
         else :
             if fused.isValid():
                solid = fused
             else:
                solid = Part.makeCompound(parts)
       else:
         solid = Part.makeCompound(parts)

    if solid.Volume < 0 : solid.reverse()
    return solid


src/GEOReverse/Modules/splitFunction.py
GEOUNED-code/GEOUNED
import sys
import FreeCAD, Part, Import

from GEOReverse.Modules.MCNPinput import MCNPinput
from GEOReverse.Modules.buildCAD import buildCAD,makeTree
from GEOReverse.Modules.Objects import CADCell
from GEOReverse.Modules.processInp import setOptions 
from GEOReverse.CodeVersion import *


def reverse(optFile='configRevese.ini') :

   printCodeVersion()

   setting = setOptions(optFile)

   mcnpfile = setting['fileIn']
   outname  = setting['fileOut']
   outBox   = setting['outBox']

   CADselection = { 'Ustart'   : setting['UStart']  ,
                    'levelMax' : setting['levelMax'], 
                    'cell'     : setting['cell']    ,
                    'mat'      : setting['mat']
                 }  

   UnivCell = CADCell()
   UnivCell.shape = UnivCell.makeBox(FreeCAD.BoundBox(*outBox))

#get geometry definition from MCNP input
   mcnp = MCNPinput(mcnpfile)

   CADCells,fails = buildCAD(UnivCell,mcnp,CADselection)

   if fails : print('failed in conversion',fails)

   CADdoc=FreeCAD.newDocument("WorkingDoc")

   makeTree(CADdoc,CADCells)
   Import.export(CADdoc.Objects[0:1],outname+'.stp' ) 
   CADdoc.saveAs(outname+'.FCStd')



def printCodeVersion():

   FreeCAD_Version = '{V[0]:}.{V[1]:}.{V[2]:}'.format(V=FreeCAD.Version())
   title = '''\
#########################################################################
#                                                                       # 
#      GEOReverse version {:<11}{}{:>26}
#      FreeCAD    version {:<11}{:>36}  
#                                                                       # 
#########################################################################'''.format(GEOReverse_Version,GEOReverse_ReleaseDate,'#',FreeCAD_Version,'#')
   print(title)


if __name__ == '__main__' :
  reverse()




src/GEOReverse/reverse.py
GEOUNED-code/GEOUNED
''' Modification from 0.9.5 
  - volInput card name substituted by volSDEF card name
  - new card volCARD (values : True/False) used to insert the CAD cell volume into the cell definition
    using the MCNP card VOL. Default value True  
  - Fixed bug. Code crashing when automatic void calculation was not enabled.
  - Fixed bug. Comment line might not be in the correct order following de CAD tree labels

Modification in 0.9.6 Release Date 24/11/2022
  - Small change in the reading of material file.
  - In "CellDefinition.py" file. "isOposite" function is call with rhe correct tolerance value instead of default value of the isOposite function.  

Modification in 0.9.7 Release Date 06/12/2022
  - Compatibility with FreeCAD version higher than 0.18.
  - fixed bug with sphere Objects

Modification in 0.9.7 Release Date 06/12/2022
  - Removed external packages Boolean. Subtituted by own make package
  - Full cell optimization using CTable approach and own factorization function
  - Cylinders instead cones can be used  as auxillary surface for Torus surface definition 
  - fix bugs several additional planes for the same reversed surface
  - new module for plane splitting in decomposition modules. All planes are considerer equivalent (PX,PY,PZ,P), parallel planes are grouped together. 
    Decomposition is performed by splitting first with group of parallel planes with lowest number of elements. 
    Old version module is still available by switching the option keyword "newSplitPlane" to False. 
  - New option added when enclosure are used. The option "sortEnclosure" True (Default False) will produce output file where solids and void of an enclosure
    are grouped together (first solid cell of the enclosure, then voids)
  - Options has been added to enlarge Box dimension when Constraint table is evaluated
 
 Modification in 0.9.8 
  - fix bug with two cylinders/cones intersection
  - Introduce splitting planes to cut rounded edges(edge made with cylinder)
  - fixed bug with torus (I hope so!)
  - new card "dummyMat" to write dummy material card (associated to material labels present in cell definition) in input file.

 new in version 0.9.8.2
  - Javi modification for memory bug for geomtry with large plane numbers
  - small changes in decomposition process
  - new verbose option to remove some output on screen

 new in version 1.0
  - allow to convert geometry to openMC XML format and script input file
  - change the entry "mcnpfile" by "geometryName" in FILES section. Now geometryName is the generic name of output geometry file.
    mncp input file will have ".mcnp" extension and openMC XML the ".xml" extension.
  - new entry "outFormat" is added in FILES section. "outFormat" is used to select the output geometry format (mcnp and/or openMC_xml). 
    Default value is only mcnp
  . new entry splitTolerance. Change the Tolerance value of the FreeCAD function split. Default Value  0.
  . new entry Verbose. Print on screen warning. Default False.
 '''

GEOUNED_Version = '1.0'
GEOUNED_ReleaseDate = '25/09/2023'


src/GEOUNED/CodeVersion.py
GEOUNED-code/GEOUNED
############################
# Module for Cell definiton #
#############################
import FreeCAD, Part
from   GEOUNED.Utils.Functions import GEOUNED_Surface
from   GEOUNED.Utils.BasicFunctions_part1  \
       import isParallel, isOposite, isInLine, signPlane, isSameValue
from    GEOUNED.Utils.booleanFunction import BoolSequence

import  GEOUNED.Utils.BasicFunctions_part2 as BF 
import GEOUNED.Utils.Qform as QF
import GEOUNED.Utils.Geometry_GU as GU
from GEOUNED.Utils.Options.Classes import Tolerances as tol
from GEOUNED.Utils.Options.Classes import Options as opt
import math
from collections import OrderedDict

def getId(facein, Surfaces):
    
    surfin = str(facein)
    if surfin == '<Plane object>' :
        if   isParallel(facein.Axis,FreeCAD.Vector(1,0,0),tol.pln_angle) :
          P = 'PX'
        elif isParallel(facein.Axis,FreeCAD.Vector(0,1,0),tol.pln_angle) :
          P = 'PY'
        elif isParallel(facein.Axis,FreeCAD.Vector(0,0,1),tol.pln_angle) :
          P = 'PZ'
        else:
          P = 'P'

        for s in Surfaces[P] :
          if BF.isSamePlane(facein,s.Surf,dtol=tol.pln_distance,atol=tol.pln_angle,relTol=tol.relativeTol) :
              return s.Index
    
    elif surfin == '<Cylinder object>' :
        for s in Surfaces['Cyl'] :
           if BF.isSameCylinder(facein,s.Surf,dtol=tol.cyl_distance,atol=tol.cyl_angle,relTol=tol.relativeTol) :  
              return s.Index 
        
    elif surfin == '<Cone object>' :
        for s in Surfaces['Cone'] :
           if BF.isSameCone(facein,s.Surf,dtol=tol.kne_distance,atol=tol.kne_angle,relTol=tol.relativeTol):
              return s.Index
         
    elif surfin[0:6] == 'Sphere':
        for s in Surfaces['Sph'] :
           if BF.isSameSphere(facein,s.Surf,tol.sph_distance,relTol=tol.relativeTol):
              return s.Index

    elif surfin == '<Toroid object>' :
        for s in Surfaces['Tor'] :
           if BF.isSameTorus(facein,s.Surf,dtol=tol.tor_distance,atol=tol.tor_angle,relTol=tol.relativeTol):
              return s.Index
                
    return 0
   
def isInverted(solid):

    face=solid.Faces[0]

    #u=(face.Surface.bounds()[0]+face.Surface.bounds()[1])/2.0 # entre 0 y 2pi si es completo
    #v=face.Surface.bounds()[0]+(face.Surface.bounds()[3]-face.Surface.bounds()[2])/3.0 # a lo largo del eje
    Range=face.ParameterRange
    u=(Range[1]+Range[0])/2.0
    v=(Range[3]+Range[2])/2.0
    
    if (str(face.Surface)=='<Cylinder object>'):
        dist1=face.Surface.value(u,v).distanceToLine(face.Surface.Center,face.Surface.Axis)
        dist2=face.Surface.value(u,v).add(face.Surface.normal(u,v).multiply(1.0e-6)).distanceToLine(face.Surface.Center,face.Surface.Axis)
        if ((dist2-dist1)<0.0):
            # The normal of the cylinder is going inside
            return True
    elif (str(face.Surface)=='<Cone object>' ):
        dist1=face.Surface.value(u,v).distanceToLine(face.Surface.Apex,face.Surface.Axis)
        dist2=face.Surface.value(u,v).add(face.Surface.normal(u,v).multiply(1.0e-6)).distanceToLine(face.Surface.Apex,face.Surface.Axis)
        if ((dist2-dist1)<0.0):
            # The normal of the cylinder is going inside
            return True
    # MIO
    elif str(face.Surface)[0:6]=='Sphere':
        # radii = point - center
        radii  = face.Surface.value(u,v).add(face.Surface.Center.multiply(-1))
        radiiB  = face.Surface.value(u,v).add(face.Surface.normal(u,v).multiply(1.0e-6)).add(face.Surface.Center.multiply(-1))
        #radiiB  = radii.add( face.Surface.normal(u,v).multiply(1.0e-6) )
        if ((radiiB.Length-radii.Length)<0.0):
            # An increasing of the radii vector in the normal direction decreases the radii: oposite normal direction
            return True
           
    elif  (str(face.Surface)=='<Plane object>'):
        dist1=face.CenterOfMass.distanceToPoint(solid.BoundBox.Center)
        dist2=face.CenterOfMass.add(face.normalAt(u,v).multiply(1.0e-6)).distanceToPoint(solid.BoundBox.Center)
        point2=face.CenterOfMass.add(face.normalAt(u,v).multiply(1.0e-6))
        if (solid.isInside(point2,1e-7,False)):
            return True
            
    return False
    

def GenPlane(face,solid):

    """ Generate an additional plane when convex surfaces of second order are presented as a face of the solid """

    
    surf = face.Surface
    if (str(surf) == '<Cylinder object>'):
        return GenPlaneCylinder(face,solid)
    if (str(surf) == '<Cone object>'):
        return GenPlaneCone(face,solid)
    
    rad=surf.Radius
    axis=surf.Axis
    pnt=surf.Center

    Same_Faces=[]
    Same_Faces.append(face)
    
    for face2 in solid.Faces:
        
# modif Pat 03/07/2023 
# here face cannot be either cone nor cylinder so if face2 is cone or cylinder it cannot be equal to face

#        if str(face2.Surface) == '<Cone object>' and not(face2.isEqual(face)):
#            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Apex.isEqual(face.Surface.Center,1e-5) and (face2.Surface.SemiAngle-face.Surface.SemiAngle)<1e-2):
                #print 'Warning: coincident cone faces are the same'
#                Same_Faces.append(face2)
        
#        if str(face2.Surface) == '<Cylinder object>' and not(face2.isEqual(face)):
#            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Radius == rad and isInLine(face2.Surface.Center,face.Surface.Axis,face.Surface.Center)):
                #print 'Warning: coincident cylinder faces are the same'
#                Same_Faces.append(face2)
                
        if str(surf)[0:6] == 'Sphere' and not(face2.isEqual(face)):
            if (face2.Surface.Center == face.Surface.Center and face2.Surface.Radius == face.Surface.Radius):
                #print 'Warning: coincident sphere faces are the same'
                Same_Faces.append(face2)

    Vertexes=[]
    #print Same_Faces
    for face3 in Same_Faces:
        for Vertex in face3.Vertexes:
            Vertexes.append(Vertex.Point)
            
    Vectors=[] # free vectors, right directed but not located
    Vectors2=[] # right coordinates from origin 
    if (str(surf) == '<Cylinder object>' or str(surf) == '<Cone object>'):
        for Point in Vertexes:
            Vec=pnt.sub(Point).projectToPlane(pnt,axis)
            Vec2=Point.projectToPlane(pnt,axis)
            Vectors.append(Vec) # free vectors
            Vectors2.append(Vec2)
    elif (str(surf)[0:6] == 'Sphere'):
        for Point in Vertexes:
            Vec=pnt.sub(Point)
            Vectors.append(Vec)
            
    angle_max=0.0
    for i,V1 in enumerate(Vectors):
        for j,V2 in enumerate(Vectors):
            angle=V1.getAngle(V2)
            if (angle > angle_max):
                Va=V1
                Vb=V2
                Va2=Vectors2[i]
                Vb2=Vectors2[j]
                angle_max=angle
                
    if (str(surf) == '<Cylinder object>'):
        normal=Va.sub(Vb).cross(surf.Axis)
    elif (str(surf) == '<Cone object>'):
        normal=Va.sub(Vb).cross(Va2.sub(surf.Apex))
    
    plane=Part.makePlane(rad,rad,pnt,normal)
    vec_on_plane = plane.Vertexes[3].Point.sub(plane.Vertexes[0].Point)
    new_pos = plane.Vertexes[0].Point.sub(vec_on_plane.multiply(10.0))
    plane_center = Part.makePlane(rad*100,rad*100,new_pos,normal)
    plane=plane_center
    
    sign= signPlane(face.Vertexes[0].Point,plane)
    normal.multiply(float(sign))
    
    plane=Part.Plane(pnt,normal).toShape()
    
    if (solid.distToShape(plane)[0]<1e-8):
        return None
    else:
        return Part.Plane(Va2,normal).toShape()
    
def GenPlaneCylinder(face,solid):

    Surf=face.Surface
    rad=Surf.Radius

    if (str(Surf)!='<Cylinder object>'):
        return None
        
    UVNodes=[]
    face_index=[solid.Faces.index(face)]
    
    try:    
        UVNodes.append(face.getUVNodes())
    except RuntimeError:
        face.tessellate(100.0)
        UVNodes.append(face.getUVNodes())
        

    for i, face2 in enumerate(solid.Faces):
              
        if face2.Area < 1e-2 : continue 
        if str(face2.Surface) == '<Cylinder object>' and not(face2.isEqual(face)):
            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Radius == rad and isInLine(face2.Surface.Center,face.Surface.Axis,face.Surface.Center)):
                #print 'Warning: coincident cylinder faces are the same'
                try:    
                    UVNodes.append(face2.getUVNodes())
                except RuntimeError:
                    face2.tessellate(100.0)
                    UVNodes.append(face2.getUVNodes())
                face_index.append(i)
    
    AngleRange=0.0
    
    Uval=[]
    
    for index in face_index:
        Range=solid.Faces[index].ParameterRange
        AngleRange=AngleRange+abs(Range[1]-Range[0])
        if not(Range[0] in Uval) and not(Range[1] in Uval): 
            Uval.append(Range[0])
            Uval.append(Range[1])
            
    if (2.0*math.pi-AngleRange<1e-2):
        return None
    
    Uval_str_cl=[]
    for i,elem1 in enumerate(Uval):
        num_str1='%11.4E' %elem1
        if abs(elem1)<1.0e-5:
            num_str1='%11.4E' %0.0
        if not(BF.isDuplicateInList(num_str1,i,Uval)):
            Uval_str_cl.append(num_str1)
    
    face_index_2=[face_index[0],face_index[0]]
    
    Node_min=UVNodes[0][0]
    Node_max=UVNodes[0][1]
    
    dif1_0=abs(float(Uval_str_cl[0])-Node_min[0])
    dif2_0=abs(float(Uval_str_cl[1])-Node_max[0])
           
    # searching for minimum and maximum angle points
    for j,Nodes in enumerate(UVNodes): 
        for elem in Nodes:
            val='%11.4E' %elem[0]
            dif1=abs(float(Uval_str_cl[0])-elem[0])
            dif2=abs(float(Uval_str_cl[1])-elem[0])

            if abs(elem[0])<1.0e-5:
                val='%11.4E' %0.0
            if (dif1<dif1_0):
                Node_min=elem
                face_index_2[0]=face_index[j]
                dif1_0=dif1
            if (dif2<dif2_0):
                Node_max=elem
                face_index_2[1]=face_index[j]
                dif2_0=dif2           
    
    V1=solid.Faces[face_index_2[0]].valueAt(Node_min[0],Node_min[1])
    V2=solid.Faces[face_index_2[1]].valueAt(Node_max[0],Node_max[1])
    
    if (V1.isEqual(V2,1e-5)):
        if opt.verbose : print('Error in the additional place definition')
        return None
        
    normal=V2.sub(V1).cross(face.Surface.Axis)
    plane=Part.Plane(V1,normal).toShape()
    
    return plane
    
def GenPlaneCone(face,solid):

    Surf=face.Surface
    rad=Surf.Radius
    Axis=face.Surface.Axis
    if (str(Surf)!='<Cone object>'):
        return None
        
    UVNodes=[]
    face_index=[]

    try:    
        UVNodes.append(face.getUVNodes())
    except RuntimeError:
        face.tessellate(100.0)
        UVNodes.append(face.getUVNodes())
        
    face_index.append(solid.Faces.index(face))

    for i, face2 in enumerate(solid.Faces):
        if face2.Area < 1e-2 : continue 
              
        if str(face2.Surface) == '<Cone object>' and not(face2.isEqual(face)):
                        
            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Apex.isEqual(face.Surface.Apex,1e-5) and (face2.Surface.SemiAngle-face.Surface.SemiAngle) < 1e-6):
                try:    
                    UVNodes.append(face2.getUVNodes())
                except RuntimeError:
                    face2.tessellate(100.0)
                    UVNodes.append(face2.getUVNodes())
                face_index.append(i)
    
    AngleRange=0.0
    
    Uval=[]
    
    for index in face_index:
        Range=solid.Faces[index].ParameterRange
        AngleRange=AngleRange+abs(Range[1]-Range[0])
        Uval.append(Range[0])
        Uval.append(Range[1])
                
    if (2.0*math.pi-AngleRange<1e-2):
        return None
    
    Uval_str_cl=[]
    
    for i,elem1 in enumerate(Uval):
        num_str1='%11.4E' %elem1
        if abs(elem1)<1.0e-5:
            num_str1='%11.4E' %0.0
        if not(BF.isDuplicateInList(num_str1,i,Uval)):
            Uval_str_cl.append(num_str1)
    
    face_index_2=[face_index[0],face_index[0]]
    
    Node_min=UVNodes[0][0]
    Node_max=UVNodes[0][1]
    dif1_0=abs(float(Uval_str_cl[0])-Node_min[0])
    dif2_0=abs(float(Uval_str_cl[1])-Node_max[0])
           
    # searching for minimum and maximum angle points
    for j,Nodes in enumerate(UVNodes): 
        for elem in Nodes:
            val='%11.4E' %elem[0]
            dif1=abs(float(Uval_str_cl[0])-elem[0])
            dif2=abs(float(Uval_str_cl[1])-elem[0])
            if abs(elem[0])<1.0e-5:
                val='%11.4E' %0.0
            if (dif1<dif1_0):
                Node_min=elem
                face_index_2[0]=face_index[j]
                dif1_0=dif1
            if (dif2<dif2_0):
                Node_max=elem
                face_index_2[1]=face_index[j]
                dif2_0=dif2
    
    V1=solid.Faces[face_index_2[0]].valueAt(Node_min[0],Node_min[1])
    V2=solid.Faces[face_index_2[1]].valueAt(Node_max[0],Node_max[1])
    
    if (V1.isEqual(V2,1e-5)):
        if opt.verbose : print('Error in the additional place definition')
        return None
       
    plane=Part.Plane(V1,V2,face.Surface.Apex).toShape()
    
    return plane


def GenTorusAnnexUPlanes(face,Uparams):
    if   isParallel(face.Surface.Axis,FreeCAD.Vector(1,0,0),tol.tor_angle) :
        axis = FreeCAD.Vector(1,0,0)
    elif isParallel(face.Surface.Axis,FreeCAD.Vector(0,1,0),tol.tor_angle) :
        axis = FreeCAD.Vector(0,1,0)
    elif isParallel(face.Surface.Axis,FreeCAD.Vector(0,0,1),tol.tor_angle) :
        axis = FreeCAD.Vector(0,0,1)

    center = face.Surface.Center
    p1 = face.valueAt(Uparams[0],0.)
    p2 = face.valueAt(Uparams[1],0.)
    pmid = face.valueAt(0.5*(Uparams[0]+Uparams[1]),0.)
        
    if isSameValue(abs(Uparams[1]-Uparams[0]),math.pi,tol.value):
        d = axis.cross(p2-p1)
        d.normalize()
        if pmid.dot(d) < 0:  d = -d
        return ((center,d,face.Surface.MajorRadius),tuple()),False

    else :
        d1 = axis.cross(p1)
        d1.normalize()
        if pmid.dot(d1) < 0:  d1 = -d1

        d2 = axis.cross(p2)
        d2.normalize()
        if pmid.dot(d2) < 0:  d2 = -d2

        if Uparams[1]-Uparams[0] < math.pi :
          return ((center,d1,face.Surface.MajorRadius,face.Surface.MajorRadius),(center,d2,face.Surface.MajorRadius,face.Surface.MajorRadius)),False # ( d1 d2 )
        else:
          return ((center,d1,face.Surface.MajorRadius,face.Surface.MajorRadius),(center,d2,face.Surface.MajorRadius,face.Surface.MajorRadius)),True  # (d1 : d2)
        
def GenTorusAnnexVSurface(face,Vparams,forceCylinder=True):
    if   isParallel(face.Surface.Axis,FreeCAD.Vector(1,0,0),tol.tor_angle) :
        axis = FreeCAD.Vector(1,0,0)
    elif isParallel(face.Surface.Axis,FreeCAD.Vector(0,1,0),tol.tor_angle) :
        axis = FreeCAD.Vector(0,1,0)
    elif isParallel(face.Surface.Axis,FreeCAD.Vector(0,0,1),tol.tor_angle) :
        axis = FreeCAD.Vector(0,0,1)
        
    p1 = face.valueAt(0.,Vparams[0])
    z1 = p1.dot(axis)
    d1 = p1.cross(axis).Length
    
    p2 = face.valueAt(0.,Vparams[1])
    z2 = p2.dot(axis)
    d2 = p2.cross(axis).Length

    if isSameValue(z1,z2,tol.distance) :
       surfType = 'Plane'
       center   = face.Surface.Center + (z1 - face.Surface.Center.dot(axis)) *axis
       Vmid = (Vparams[0]+Vparams[1])*0.5
       pMid = face.valueAt(0,Vmid)
       if pMid.dot(axis) < z1 :
           inSurf = True
       else:
           inSurf = False
       return (center,axis,face.Surface.MajorRadius,face.Surface.MajorRadius),surfType,inSurf        

    elif isSameValue(d1,d2,tol.distance) or forceCylinder :
       surfType = 'Cylinder'
       radius   = min(d1,d2)
       center   = face.Surface.Center
       if isSameValue(d1,face.Surface.MajorRadius,tol.distance) :
           Vmid = (Vparams[0]+Vparams[1])*0.5
           pMid = face.valueAt(0,Vmid)
           if pMid.cross(axis).Length < face.Surface.MajorRadius :
               inSurf = True
           else:
               inSurf = False
       else:
           if d1 < face.Surface.MajorRadius :
               inSurf = True
           else:    
               inSurf = False
       return (center,axis,radius,face.Surface.MinorRadius),surfType,inSurf
    
    else :
       surfType = 'Cone'
       zc = (d1*z2-d2*z1)/(d1-d2)
       Apex   = face.Surface.Center + (zc - face.Surface.Center.dot(axis)) *axis
       semiAngle = abs(math.atan(d1/(z1-zc)))
       if z1-zc < 0 :
          ConeAxis = -axis
       else:
          ConeAxis = axis 

       Vmid = (Vparams[0]+Vparams[1])*0.5
       pMid = face.valueAt(0,Vmid)
       zMid = pMid.dot(axis) 
       dMid = pMid.cross(axis).Length
       dCone= d1*(zMid-zc)/(z1-zc)
       if dMid < dCone :
          inSurf = True
       else:
          inSurf = False          
       return (Apex,ConeAxis,semiAngle,face.Surface.MinorRadius,face.Surface.MajorRadius),surfType,inSurf      



def cellDef(metaObj,Surfaces,UniverseBox):
  
    solids=metaObj.Solids
    delList = []

    PieceDef = BoolSequence(operator='OR')
    PieceObj = []
    for isol,solid in enumerate(solids):
        SurfPiece = []
        SurfObj   = []
        extraPlaneReverse = dict()

        flag_inv=isInverted(solid)
        solid_GU=GU.solid_GU(solid)
        lastTorus = -1
        for iface,face in enumerate(solid_GU.Faces):
            if abs(face.Area) < 1e-2 : continue 
            if face.Area < 0 :
               if opt.verbose : print('Warning : Negative surface Area')
            if face.Orientation not in ('Forward','Reversed') : continue
            if (flag_inv):
                orient_temp=face.Orientation
                if orient_temp=='Forward':
                    orient='Reversed'
                elif orient_temp=='Reversed':
                    orient='Forward'
            else:
                orient=face.Orientation

            surfaceType = str(face.Surface)
            if 'Sphere' in surfaceType : surfaceType = 'Sphere' 
            
            if (  surfaceType in ('<Cylinder object>','<Cone object>','Sphere') and orient=='Reversed'):
                if opt.verbose : print('Warning: Convex Cylinder, Sphere or Cone additional plane could be created')
                if (surfaceType == '<Cone object>'):
                                                                                      
                    if (isParallel(face.Surface.Axis,FreeCAD.Vector(1,0,0),tol.angle) or \
                        isParallel(face.Surface.Axis,FreeCAD.Vector(0,1,0),tol.angle) or \
                        isParallel(face.Surface.Axis,FreeCAD.Vector(0,0,1),tol.angle)):
                        if opt.verbose : print('Cone parallel to X, Y or Z not GQ is used')
                        id=getId(face.Surface,Surfaces) 
                        if (not(str(id) in SurfPiece)):
                            SurfPiece.append(str(id))
                            SurfObj.append(face)
                    else:
                        dim1 = face.Surface.Radius
                        dim2 = face.Surface.Radius
                        plane = GEOUNED_Surface(('Plane',(face.Surface.Apex,face.Surface.Axis,dim1,dim2)),UniverseBox,Face='Build')
                        id2,exist = Surfaces.addPlane(plane)
                        id = getId(face.Surface,Surfaces)

                        if exist :
                           p = Surfaces.getSurface(id2)
                           if isOposite(plane.Surf.Axis,p.Surf.Axis,tol.pln_angle):
                             id2= -id2
                        var='(%i:%i)' %(id,-id2)
                        if (not(var in SurfPiece)):
                            SurfPiece.append(var)
                            SurfObj.append([face,plane.shape])
                else:
                    id=getId(face.Surface,Surfaces)
                    if (not(str(id) in SurfPiece)):
                        SurfPiece.append(str(id))
                        SurfObj.append(face)
                        
                try:
                    plane=GenPlane(face,solid_GU)
                    if plane is not None:
                       plane=GU.Plane_GU(plane)    
                except:
                    plane=None
                    if opt.verbose : print('Warning: generation of additional plane has failed')
                
                idFace = id
                
                if plane is not None : 
                  p = GEOUNED_Surface(('Plane',(plane.Position,plane.Axis,plane.dim1,plane.dim2)),UniverseBox,Face='Build')
                  
                  id,exist = Surfaces.addPlane(p)
                  sign = signPlane(face.CenterOfMass,p)
                  if exist :
                     pp   = Surfaces.getSurface(id)
                     if isOposite(p.Surf.Axis,pp.Surf.Axis,tol.angle):
                          id= -id
                  id *= sign
                   
                  if ( idFace not in extraPlaneReverse.keys()):
                     extraPlaneReverse[idFace] = [str(id)]
                     SurfObj.append(p.shape)
                  else:
                     if str(id) not in extraPlaneReverse[idFace] :  
                       extraPlaneReverse[idFace].append(str(id))
                       SurfObj.append(p.shape)

                  #if (not(str(id) in SurfPiece)):
                     #SurfPiece.append(str(id))
                     #SurfObj.append(p.shape)

                if surfaceType == '<Cylinder object>' and False:    # temporary disabled doesn't work properly
                   PlanesId = ExtraPlaneCylFace(face,UniverseBox,Surfaces)
                   for id in PlanesId:
                     var = id
                     if (not(str(var) in SurfPiece)):
                       SurfPiece.append(str(var))
                       SurfObj.append(Surfaces.getSurface(abs(id)).shape)

            elif (surfaceType == '<Cone object>'): # this only if GQ is used
                if (isParallel(face.Surface.Axis,FreeCAD.Vector(1,0,0),tol.angle) or \
                    isParallel(face.Surface.Axis,FreeCAD.Vector(0,1,0),tol.angle) or \
                    isParallel(face.Surface.Axis,FreeCAD.Vector(0,0,1),tol.angle)):
                    #print 'Cone parallel to X, Y or Z not GQ is used'
                    id=getId(face.Surface,Surfaces)
                    id= -id 
                    if (not(str(id) in SurfPiece)):
                        SurfPiece.append(str(id))
                        SurfObj.append(face)
                else:
                    dim1 = face.Surface.Radius
                    dim2 = face.Surface.Radius
                    plane = GEOUNED_Surface(('Plane',(face.Surface.Apex,face.Surface.Axis,dim1,dim2)),UniverseBox,Face='Build')
                    id2,exist = Surfaces.addPlane(plane)
                    id1 = getId(face.Surface,Surfaces)

                    if exist :
                       p = Surfaces.getSurface(id2)
                       if isOposite(plane.Surf.Axis,p.Surf.Axis,tol.pln_angle):
                         id2= -id2
                    var='%i %i' %(-id1,id2)
                    if (not(var in SurfPiece)):
                        SurfPiece.append(var)
                        SurfObj.append([face,plane.shape])
                   
            elif (surfaceType == '<Toroid object>') :
                
                if (isParallel(face.Surface.Axis,FreeCAD.Vector(1,0,0),tol.angle) or \
                    isParallel(face.Surface.Axis,FreeCAD.Vector(0,1,0),tol.angle) or \
                    isParallel(face.Surface.Axis,FreeCAD.Vector(0,0,1),tol.angle)):
                 
                    id1=getId(face.Surface,Surfaces)
                    
                    if (orient == 'Forward'):

                        index,Uparams = solid_GU.TorusUParams[iface]
                        if index == lastTorus : continue
                        lastTorus = index
                        var = '-%i' %id1

                       # Old version seems not work porperly

                       # index,Uparams = solid_GU.TorusUParams[iface]
                       # if index == lastTorus : continue
                       # lastTorus = index
                       # closed,UminMax = Uparams
                       # if closed :
                       #    var = '-%i' %id1
                       # else:
                       #    (params1,params2),addOR = GenTorusAnnexUPlanes(face,UminMax)
                       #    plane = GEOUNED_Surface(('Plane',params1),UniverseBox,Face='Build')
                       #    ip1,exist = Surfaces.addPlane(plane)
                       #    #plane.shape.exportStep('p{}.stp'.format(ip1))
                       #    if exist :
                       #       p = Surfaces.getSurface(ip1)
                       #       if isOposite(plane.Surf.Axis,p.Surf.Axis,tol.pln_angle):
                       #          ip1=-ip1

                       #    if params2 : 
                       #       plane = GEOUNED_Surface(('Plane',params2),UniverseBox,Face='Build')
                       #       ip2,exist = Surfaces.addPlane(plane)
                       #       #plane.shape.exportStep('p{}.stp'.format(ip2))
                       #       if exist :
                       #          p = Surfaces.getSurface(ip2)
                       #          if isOposite(plane.Surf.Axis,p.Surf.Axis,tol.pln_angle):
                       #             ip2=-ip2

                       #       if addOR :                      
                       #          var = '-%i (%i:%i)' %(id1,ip1,ip2)
                       #       else:   
                       #          var = '-%i %i %i' %(id1,ip1,ip2)
                       #    else:
                       #        var = '-%i %i' %(id1,ip1)
                    else:
                        index,Vparams = solid_GU.TorusVParams[iface]
                        if index == lastTorus : continue
                        lastTorus = index
                        closed,VminMax = Vparams
                        if closed :
                           var = '%i' %id1
                        else:
                           surfParams,surfType,inSurf = GenTorusAnnexVSurface(face,VminMax,opt.forceCylinder)

                           if surfType == 'Cone' :
                              cone = GEOUNED_Surface(('Cone',surfParams),UniverseBox,Face='Build')
                              id2,exist = Surfaces.addCone(cone)
                                                     
                           elif surfType == 'Cylinder' :
                              cyl = GEOUNED_Surface(('Cylinder',surfParams),UniverseBox,Face='Build')
                              id2,exist = Surfaces.addCylinder(cyl)
                              #cyl.shape.exportStep('cyl{}.stp'.format(id2))
                                                    
                           elif surfType == 'Plane' :
                              plane = GEOUNED_Surface(('Plane',surfParams),UniverseBox,Face='Build')
                              id2,exist = Surfaces.addPlane(plane)
                              if exist :
                                 p = Surfaces.getSurface(id2)
                                 if isOposite(plane.Surf.Axis,p.Surf.Axis,tol.pln_angle):
                                     id2=-id2

                           var = '%i %i' %(id1,-id2 if inSurf else id2)

                    if (not(var in SurfPiece)):
                        SurfPiece.append(var)
                        SurfObj.append(face)
                else:
                    if opt.verbose : print('Only Torus with axis along X, Y , Z axis can be reproduced')
            else:
                id=getId(face.Surface,Surfaces)
                surf = face
                if id == 0:
                    if opt.verbose : print('Warning: ', surfaceType, ' not found in surface list')
                    if surfaceType == '<Plane object>' :
                      dim1 = face.ParameterRange[1]-face.ParameterRange[0]
                      dim2 = face.ParameterRange[3]-face.ParameterRange[2]  
                      plane = GEOUNED_Surface(('Plane',(face.Surface.Position,face.Surface.Axis,dim1,dim2)),UniverseBox,Face='Build')
                      id,exist    = Surfaces.addPlane(plane)
                      surf  = plane.shape
                    elif surfaceType == '<Cylinder object>':
                      dimL = face.ParameterRange[3]-face.ParameterRange[2]  
                      cylinder = GEOUNED_Surface(('Cylinder',(face.Surface.Center,face.Surface.Axis,face.Surface.Radius,dimL)),UniverseBox,Face='Build')
                      id,exist    = Surfaces.addCylinder(cylinder)
                      surf  = cylinder.shape
               
                if (orient == 'Reversed'):
                    var=id
                elif (orient == 'Forward'):
                    var=-id

                if  surfaceType == '<Plane object>':
                   s = Surfaces.getSurface(id)
                   if  isOposite(face.Surface.Axis,s.Surf.Axis,tol.pln_angle) :
                      var=-var
                       
                if (str(var) in SurfPiece):
                    continue

                if (not(str(var) in SurfPiece)):
                    SurfPiece.append(str(var))
                    SurfObj.append(surf)

        if extraPlaneReverse :
          for extra in extraPlaneReverse.values() :
             if len(extra) ==1 :
                if extra[0] not in SurfPiece:
                   SurfPiece.append(extra[0])
             else:
                SurfPiece.append('({})'.format(':'.join(extra)))

        SurfPieceBool = BoolSequence(' '.join(SurfPiece))
        # possible expresion for e
        #  i1
        #  i1 i2SurfPiece
        #  i1 i2 i3
        #  (i1:i2)
        #  i1 (i2:i3)
        
 #         semi = e.find(':')
 #         blk  = e.find(' ')
 #         print (e)
 #         if semi != -1 :
 #           orTerm = expOR(int(e[1:semi]),int(e[semi+1:-1]))
 #           SurfPieceBool.add(orTerm)  
 #         elif blk != -1 :  
 #           SurfPieceBool.add(int(e[1:blk]),int(e[blk+1:-1]))
 #         else :
 #          SurfPieceBool.add(int(e))

        if SurfPieceBool.elements : 
          PieceDef.append(SurfPieceBool)
          PieceObj.append(SurfObj)
        else:
          delList.append(isol)

    for isol in reversed(delList) :
       del metaObj.Solids[isol]
    metaObj.setDefinition(PieceDef)
    metaObj.setFaces(PieceObj)
    return 
    
def ExtraPlaneCylFace(face,Box,Surfaces):
    wire = face.OuterWire
    planesId = []
    for e in wire.OrderedEdges :
       curve = str(e.Curve)
       if (curve[0:6] == 'Circle' or curve == '<Ellipse object>' ):
          dir = e.Curve.Axis
          center = e.Curve.Center
          if curve == '<Ellipse object>' :
            dim1   = e.Curve.MinorRadius
            dim2   = e.Curve.MajorRadius
          else:    
            dim1   = e.Curve.Radius
            dim2   = e.Curve.Radius
          plane = GEOUNED_Surface(('Plane',(center,dir,dim1,dim2)),Box,Face='Build')
          sign = signPlane(face.CenterOfMass,plane)
          id,exist = Surfaces.addPlane(plane)
          if exist :
             pp   = Surfaces.getSurface(id)
             if isOposite(plane.Surf.Axis,pp.Surf.Axis,tol.pln_angle):
                 id=-id
          planesId.append(id)
    return planesId
         

src/GEOUNED/Conversion/CellDefinition.py
GEOUNED-code/GEOUNED
#   Conversion to MCNP v0.0 
#   Only one solid and planar surfaces
#

import FreeCAD, Part
import BOPTools.SplitAPI
import GEOUNED.Utils.Functions as UF
import GEOUNED.Utils.Geometry_GU as GU
import GEOUNED.Conversion.CellDefinition as CD
from collections import OrderedDict
from GEOUNED.Utils.BasicFunctions_part1 import isParallel, isSameValue,\
     isInLine, isInPlane
from GEOUNED.Utils.BasicFunctions_part2 import isDuplicateInList
from GEOUNED.Utils.Options.Classes import Tolerances as tol
from GEOUNED.Utils.Options.Classes import Options as opt
import math
import copy

twoPi = math.pi*2
    
def splitFullCylinder(solid):
    explode = []
    Bases = [solid]
    while True:
        newBases = []
        for base in Bases:
            cutSolids = cutFullCylinder(base)
            if len(cutSolids) == 1:
                explode.extend(cutSolids)
            else:
                newBases.extend(cutSolids)
        if len(newBases) == 0 :
            break
        else:
            Bases = newBases
         
    return Part.makeCompound(explode)

def cutFullCylinder(solid):
    solid_GU = GU.solid_GU(solid)
    Surfaces=UF.Surfaces_dict()    
    flag_inv = CD.isInverted(solid_GU.solid)
    UniverseBox = solid.BoundBox

    for face in solid_GU.Faces:
       surf = str(face.Surface)
       if surf == '<Cylinder object>' :
          if (flag_inv):
              orient = 'Reversed' if face.Orientation =='Forward' else 'Forward'
          else:
              orient=face.Orientation
    
          u1,u2,v1,v2 = face.ParameterRange
          angle = abs(u2-u1)

          # closed convex cylinder
          if abs(angle%twoPi) < 1e-2 and orient=='Forward':
             dir  = face.Surface.Axis
             orig = face.Surface.Center
             rad  = face.Surface.Radius
             dimL = face.ParameterRange[3]-face.ParameterRange[2]
             cylinder = UF.GEOUNED_Surface(('Cylinder',(orig,dir,rad,dimL)),UniverseBox)
             cylinder.buildSurface()
             Surfaces.addCylinder(cylinder,False)

             #add planes if cylinder axis is cut by a plane (plane quasi perpedicular to axis)
             for p in CylBoundPlanes(face,UniverseBox) :
                p.buildSurface()
                Surfaces.addPlane(p,False)
             break
                
    Planes = [] 
    for P in ('PX','PY','PZ','P') : Planes.extend(Surfaces[P])
    Planes = sortPlanes(Planes,True)

    
    if len(Planes) == 0 : return [solid]
    if len(Planes[-1]) < opt.nPlaneReverse : Planes.reverse()

    cut = False
    for pp in Planes :
       # cut with more external parallel planes
       pp[0].buildSurface()
       pp[-1].buildSurface()
       tools = (pp[0].shape, pp[-1].shape) 
       comsolid=BOPTools.SplitAPI.slice(solid,tools,'Split', tolerance = opt.splitTolerance)
       if len(comsolid.Solids) > 1 :
         outSolid = comsolid.Solids
         cut = True
         break

    if cut : return outSolid

    tool = (Surfaces['Cyl'][0].shape,)
    comsolid=BOPTools.SplitAPI.slice(solid,tool,'Split', tolerance = opt.splitTolerance)

    if len(comsolid.Solids) > 1 :
       outSolid = comsolid.Solids
    else:
       outSolid = [solid]

    return outSolid   


def GenPlane(pos,normal,diag):
    plane = Part.makePlane(diag,diag,pos,normal)
    vec_on_plane= plane.Vertexes[3].Point.sub(plane.Vertexes[0].Point)
    new_pos = plane.Vertexes[0].Point.sub(vec_on_plane)
    plane_center = Part.makePlane(2.0*diag,2.0*diag,new_pos,normal)
    return plane_center

def CylBoundPlanes(face,boundBox):
    Edges=face.OuterWire.Edges
    planes = []
    for e in Edges:
      try:
         curve = str(e.Curve)
      except:
         curve = 'none'
         
      if curve[0:6] == 'Circle' :
          dir = e.Curve.Axis
          center = e.Curve.Center
          dim1 = e.Curve.Radius
          dim2 = e.Curve.Radius
          plane = UF.GEOUNED_Surface(('Plane',(center,dir,dim1,dim2)),boundBox)
          planes.append(plane)
        
      elif curve == '<Ellipse object>' :    
          dir = e.Curve.Axis
          center = e.Curve.Center
          dim1 = e.Curve.MinorRadius
          dim2 = e.Curve.MajorRadius
          plane = UF.GEOUNED_Surface(('Plane',(center,dir,dim1,dim2)),boundBox)
          planes.append(plane)   

    return planes


def TorusBoundPlanes(face,boundBox):
    params = face.ParameterRange
    planes = []
    if isSameValue(params[1]-params[0],twoPi,tol.value) : return planes
    
    Edges=face.OuterWire.Edges
    
    for e in Edges:
      try:
         curve = str(e.Curve)
      except:
         curve = 'none'
   
      if curve[0:6] == 'Circle' :
          dir = e.Curve.Axis
          if not isParallel (dir,face.Surface.Axis,tol.angle) :
            center = e.Curve.Center
            dim1 = e.Curve.Radius
            dim2 = e.Curve.Radius
            plane = UF.GEOUNED_Surface(('Plane',(center,dir,dim1,dim2)),boundBox)
            planes.append(plane)
        
      elif curve == '<Ellipse object>' :    
          dir = e.Curve.Axis
          center = e.Curve.Center
          dim1 = e.Curve.MinorRadius
          dim2 = e.Curve.MajorRadius
          plane = UF.GEOUNED_Surface(('Plane',(center,dir,dim1,dim2)),boundBox)
          planes.append(plane)

      elif curve == '<BSplineCurve object>' :
          planeParams = PlaneSplineCurve(e)
          if planeParams is not None:
             plane = UF.GEOUNED_Surface(('Plane',planeParams),boundBox)
             planes.append(plane) 

    return planes

def PlaneSplineCurve(edge):

    normal = edge.derivative1At(0).cross(edge.derivative1At(0.5))
    normal.normalize()
    curve2D = True
    for p in (0.25,0.75,1) :
       # check if derivative orthogonal to curve normal vector 
       if abs(normal.dot(edge.derivative1At(p))) > tol.value :
           curve2D = False
           break

    r = edge.valueAt(0.25)-edge.valueAt(0.75)
    if curve2D:    
       return (edge.valueAt(0),normal,r.Length,r.Length)
    else:
       return None 


def ExtractSurfaces(solid,kind,UniverseBox,MakeObj=False):
    if kind == 'All':
       fuzzy = True 
       solidParts = []
       for s in solid.Solids:
         solidParts.append(GU.solid_GU(s))
    else:
        fuzzy = False
        solidParts = [GU.solid_GU(solid)] 

    ex = FreeCAD.Vector(1,0,0)
    ey = FreeCAD.Vector(0,1,0)
    ez = FreeCAD.Vector(0,0,1)    
    Surfaces=UF.Surfaces_dict()    

    for solid_GU in solidParts:
       flag_inv = CD.isInverted(solid_GU.solid)
     
       # Get the parameter of all faces of the solid
       # Add auxillary planes for Cylinders and Cones                    
       for face in solid_GU.Faces:
         surf = str(face.Surface)
         if surf == '<Plane object>' and kind in ['Planes','All'] :
            normal = face.Surface.Axis
            pos=face.CenterOfMass
            dim1 = face.ParameterRange[1]-face.ParameterRange[0]
            dim2 = face.ParameterRange[3]-face.ParameterRange[2]
            plane = UF.GEOUNED_Surface(('Plane',(pos,normal,dim1,dim2)),UniverseBox)
            if MakeObj : plane.buildSurface()
            Surfaces.addPlane(plane,fuzzy)
    
         elif surf == '<Cylinder object>' :
           dir  = face.Surface.Axis
           orig = face.Surface.Center
           rad  = face.Surface.Radius
           dimL = face.ParameterRange[3]-face.ParameterRange[2]
           if kind in ['Cyl','All'] :
             cylinder = UF.GEOUNED_Surface(('Cylinder',(orig,dir,rad,dimL)),UniverseBox)
             if MakeObj : cylinder.buildSurface()
             Surfaces.addCylinder(cylinder,fuzzy)

           if kind in ['Planes','All'] :
             #add planes if cylinder axis is cut by a plane (plane quasi perpedicular to axis)
             for p in CylBoundPlanes(face,UniverseBox) :
                 if MakeObj : p.buildSurface()
                 Surfaces.addPlane(p)

         elif ( surf == '<Cone object>') :
           dir = face.Surface.Axis
           apex = face.Surface.Apex
           halfAngle = face.Surface.SemiAngle         
           dimL = face.ParameterRange[3]-face.ParameterRange[2]
           dimR = face.Surface.Radius
           if kind in ['Cone','All'] :
             cone = UF.GEOUNED_Surface(('Cone',(apex,dir,halfAngle,dimL,dimR)),UniverseBox)
             if MakeObj : cone.buildSurface()
             Surfaces.addCone(cone)
        
           if kind in ['Planes','All'] :
             for p in CylBoundPlanes(face,UniverseBox) :
                 if MakeObj : p.buildSurface()
                 Surfaces.addPlane(p)

         elif surf[0:6] == 'Sphere' and kind in ['Sph','All']:
           rad = face.Surface.Radius
           pnt = face.Surface.Center
           sphere = UF.GEOUNED_Surface(('Sphere',(pnt,rad)),UniverseBox)
           if MakeObj : sphere.buildSurface() 
           Surfaces.addSphere(sphere)
         
           if kind in ['Planes','All'] :
             for p in CylBoundPlanes(face,UniverseBox) :
                 if MakeObj : p.buildSurface()
                 Surfaces.addPlane(p)

         elif (surf == '<Toroid object>'):
           if  kind in ['Tor','All']:
              radMaj = face.Surface.MajorRadius
              radMin = face.Surface.MinorRadius
              center = face.Surface.Center
              dir    = face.Surface.Axis
              torus = UF.GEOUNED_Surface(('Torus',(center,dir,radMaj,radMin)),UniverseBox)
              if MakeObj : torus.buildSurface() 
              Surfaces.addTorus(torus)

           if kind in ['Planes','All'] :
              for p in TorusBoundPlanes(face,UniverseBox) :
                 if MakeObj : p.buildSurface()
                 Surfaces.addPlane(p)
         
    return Surfaces                  
 
    
def isAlreadyInPlanes(plane,Array):

    for elem in Array:
        if (plane.Surface.Axis.cross(elem.Surface.Axis)==FreeCAD.Vector(0,0,0) and isInPlane(plane.Surface.Position,elem.Surface)):
            return True
    
    return False
    
#
#
#   Check if to faces are joint
#    
def ContiguousFace(face1,face2):
    return face1.distToShape(face2)[0] < tol.distance
    
def SameFaces(Faces):
    Connection=OrderedDict()
    if len(Faces)==1:
        return []
    
    for i, face1 in enumerate(Faces):
        Couples=[]
        if not Faces[i+1:]:
            continue
        for j, face2 in enumerate(Faces[i+1:]):
            if ContiguousFace(face1,face2):
                
                Couples.append(i+1+j)
                
        Connection[i]=Couples
                      
    lista=Connection[0]
    Connection.popitem(0)
    
    if len(Connection)==0: # solo los elementos de la lista de la superficie 0
        return lista
    
    if not lista: # ninguna face está conecta conecta con la superficie 0
        return lista
    
    for elem in Connection:
        if elem in lista: # que la key esta en lista implica que sus dependencias estan
            lista.extend(Connection[elem])
        else:
            for elem2 in Connection[elem]:
                if elem2 in lista: # si una de sus dependencias esta en lista lo esta la clave
                    lista.append(elem)

    return list(set(lista))
    
# Tolerance in this function are not the general once
# function should be reviewed
def GenPlaneCylinder(face,solid):
    Surf=face.Surface
    rad=Surf.Radius
    if face.Area < 1e-2:
        return None
    
    UVNodes=[] 
    face_index_0=[solid.Faces.index(face)]

    try:
        face.tessellate(0.1)
        UVNodes.append(face.getUVNodes())
    except RuntimeError:
        PR=face.ParameterRange
        UVNode1=(PR[0],PR[2])
        UVNode2=(PR[1],PR[3])
        UVNodes.append([UVNode1,UVNode2])
        
    
    for i, face2 in enumerate(solid.Faces):
              
        if str(face2.Surface) == '<Cylinder object>' and not(face2.isEqual(face)):
            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Radius == rad and isInLine(face2.Surface.Center,face.Surface.Axis,face.Surface.Center)):
                face_index_0.append(i)
                            
    # prueba SameFaces, parece ok
    Faces_p=[]
    for ind in face_index_0:
        Faces_p.append(solid.Faces[ind])
    
    face_index=[face_index_0[0]] # la face de entrada

    for k in SameFaces(Faces_p):
        face_index.append(face_index_0[k])
    
    # commented to let plane cut rounded corner 
    # if len(face_index_0)==len(face_index): #Esto evita un corte creo!!! no debería ser así en conversion
    #    return None
    
    for ind in reversed(face_index[1:]):
        if solid.Faces[ind].Area<=1e-3:
            face_index.remove(ind)

        else:
            face2 = solid.Faces[ind]
            try:
                face2.tessellate(0.1)
                UVNodes.append(face2.getUVNodes())
            except RuntimeError:
                PR=face2.ParameterRange
                UVNode1=(PR[0],PR[2])
                UVNode2=(PR[1],PR[3])
                UVNodes.append([UVNode1,UVNode2])
    
    AngleRange=0.0
    
    Uval=[]
    for index in face_index:
        Range=solid.Faces[index].ParameterRange
        AngleRange=AngleRange+abs(Range[1]-Range[0])
        #if not(Range[0] in Uval) and not(Range[1] in Uval): 
        Uval.append(Range[0])
        Uval.append(Range[1])
             
    if (twoPi-AngleRange < 1.e-2 or AngleRange < 1.e-2):
        return None
    
    Uval_str_cl=[]
    
    for i,elem1 in enumerate(Uval):
        num_str1='%11.4E' %elem1
        if abs(elem1)<1.0e-5:
            num_str1='%11.4E' %0.0
    
        if not(isDuplicateInList(num_str1,i,Uval)):
            Uval_str_cl.append(num_str1)
    
    if len(Uval_str_cl) < 2:
        if opt.verbose : print ('GenPlaneCylinder : Uval_str_cl should no be void ')
        return None

    face_index_2=[face_index[0],face_index[0]]
    
    Node_min=UVNodes[0][0]
    Node_max=UVNodes[0][1]
  
    dif1_0=abs(float(Uval_str_cl[0])-Node_min[0])
    dif2_0=abs(float(Uval_str_cl[1])-Node_max[0])
           
    # searching for minimum and maximum angle points
   
    for j,Nodes in enumerate(UVNodes): 
        for elem in Nodes:
            val='%11.4E' %elem[0]
            dif1=abs(float(Uval_str_cl[0])-elem[0])
            dif2=abs(float(Uval_str_cl[1])-elem[0])

            if abs(elem[0])<1.0e-5:
                val='%11.4E' %0.0
            if (dif1<dif1_0):
                Node_min=elem
                face_index_2[0]=face_index[j]
                dif1_0=dif1
            if (dif2<dif2_0):
                Node_max=elem
                face_index_2[1]=face_index[j]
                dif2_0=dif2           
    
    V1=solid.Faces[face_index_2[0]].valueAt(Node_min[0],Node_min[1])
    V2=solid.Faces[face_index_2[1]].valueAt(Node_max[0],Node_max[1])
    
    if (V1.isEqual(V2,1e-5)):
        if opt.verbose : print('Error in the additional plane definition')
        return None
    
    normal=V2.sub(V1).cross(face.Surface.Axis)
    
    plane=Part.Plane(V1,normal).toShape()
    
    return plane

# Tolerance in this function are not the general once
# function should be reviewed    
def GenPlaneCone(face,solid):

    Surf=face.Surface
    rad=Surf.Radius
    
    if face.Area < 1e-2:
        return None
           
    UVNodes=[] 
    face_index_0=[solid.Faces.index(face)]

    try:
        face.tessellate(0.1)
        UVNodes.append(face.getUVNodes())
    except RuntimeError:
        PR=face.ParameterRange
        UVNode1=(PR[0],PR[2])
        UVNode2=(PR[1],PR[3])
        UVNodes.append([UVNode1,UVNode2])

    for i, face2 in enumerate(solid.Faces):
              
        if str(face2.Surface) == '<Cone object>' and not(face2.isEqual(face)):
                        
            if (face2.Surface.Axis.isEqual(face.Surface.Axis,1e-5) and face2.Surface.Apex.isEqual(face.Surface.Apex,1e-5) and (face2.Surface.SemiAngle-face.Surface.SemiAngle) < 1e-6):

                face_index_0.append(i)
    
    Faces_p=[]
    for ind in face_index_0:
        Faces_p.append(solid.Faces[ind])
    
    face_index=[face_index_0[0]] # la face de entrada
    
    for k in SameFaces(Faces_p):
        face_index.append(face_index_0[k])
     
    # same as cylinder commennt     
    #if len(face_index_0)==len(face_index):
    #    return None
    
    for ind in reversed(face_index[1:]):
        if solid.Faces[ind].Area<=1e-3:
            face_index.remove(ind)
        else:
            face2 = solid.Faces[ind]
            try:
                face2.tessellate(0.1)
                UVNodes.append(face2.getUVNodes())
            except RuntimeError:
                PR=face2.ParameterRange
                UVNode1=(PR[0],PR[2])
                UVNode2=(PR[1],PR[3])
                UVNodes.append([UVNode1,UVNode2])
    
    AngleRange=0.0
    
    Uval=[]
     
    for index in face_index:
        Range=solid.Faces[index].ParameterRange
        AngleRange=AngleRange+abs(Range[1]-Range[0])
        Uval.append(Range[0])
        Uval.append(Range[1])
    if (twoPi-AngleRange < 1.e-2 or AngleRange<1e-2):
        return None
    
    Uval_str_cl=[]
    
    for i,elem1 in enumerate(Uval):
        num_str1='%11.4E' %elem1
        if abs(elem1)<1.0e-5:
            num_str1='%11.4E' %0.0
        if not(isDuplicateInList(num_str1,i,Uval)):
            Uval_str_cl.append(num_str1)
    
    if len(Uval_str_cl) < 2:
        if opt.verbose : print ('GenPlaneCone : Uval_str_cl should no be void ')
        return None

    face_index_2=[face_index[0],face_index[0]]
    
    Node_min=UVNodes[0][0]
    Node_max=UVNodes[0][1]
    dif1_0=abs(float(Uval_str_cl[0])-Node_min[0])
    dif2_0=abs(float(Uval_str_cl[1])-Node_max[0])
           
    # searching for minimum and maximum angle points
    for j,Nodes in enumerate(UVNodes): 
        for elem in Nodes:
            val='%11.4E' %elem[0]
            dif1=abs(float(Uval_str_cl[0])-elem[0])
            dif2=abs(float(Uval_str_cl[1])-elem[0])

            if abs(elem[0])<1.0e-5:
                val='%11.4E' %0.0
            if (dif1<dif1_0):
                Node_min=elem
                face_index_2[0]=face_index[j]
                dif1_0=dif1
            if (dif2<dif2_0):
                Node_max=elem
                face_index_2[1]=face_index[j]
                dif2_0=dif2
    
    V1=solid.Faces[face_index_2[0]].valueAt(Node_min[0],Node_min[1])
    V2=solid.Faces[face_index_2[1]].valueAt(Node_max[0],Node_max[1])
    
    if (V1.isEqual(V2,1e-5)):
        if opt.verbose : print('Error in the additional plane definition')
        return None
    
    #normal=V2.sub(V1).cross(face.Surface.Axis)
    
    #plane=Part.Plane(V1,normal).toShape()
    plane=Part.Plane(V1,V2,face.Surface.Apex).toShape()
    
    return plane
    
def Plane2ndOrder(solid_GU,face,flag_inv,convex=True):
    planes = []

    if face is None :
       for face in solid_GU.Faces:
          if (flag_inv):
              orient_temp=face.Orientation
              if orient_temp=='Forward':
                  orient='Reversed'
              elif orient_temp=='Reversed':
                  orient='Forward'
          else:
              orient=face.Orientation
    
          if convex and orient=='Reversed': # convex face condition
              pp = None
              if str(face.Surface) == '<Cylinder object>': 
                  pp = GenPlaneCylinder(face,solid_GU)
              elif str(face.Surface) == '<Cone object>':
                  pp = GenPlaneCone(face,solid_GU)
              if pp is not None : 
                planes.append(pp)
            #elif str(face.Surface)[0:6] == 'Sphere':
            #    return GenPlaneSphere(face,solid_GU) 

    else:
       if (flag_inv):
           orient_temp=face.Orientation
           if orient_temp=='Forward':
               orient='Reversed'
           elif orient_temp=='Reversed':
               orient='Forward'
       else:
           orient=face.Orientation

       if not convex and orient=='Forward':
            if str(face.Surface) == '<Cylinder object>': 
               pp = GenPlaneCylinder(face,solid_GU)
            elif str(face.Surface) == '<Cone object>':
               pp = GenPlaneCone(face,solid_GU)
            if pp is not None : 
               planes.append(pp)

    return planes

def SplitPlanes(Solids,UniverseBox,newVersion=True):
    if newVersion :
        return SplitPlanes_new(Solids,UniverseBox)
    else:
        return SplitPlanes_org(Solids,UniverseBox)

def SplitPlanes_new(Solids,UniverseBox):
    Bases = Solids[:]
    simpleSolid = []

    # Cut with real planes defining solid faces
    while True: 
      newBases = []
      for base in Bases:
         cutSolids = splitPPlanes_new(base,UniverseBox)
         if len(cutSolids) == 1:
           simpleSolid.extend(cutSolids)
         else:
           newBases.extend(cutSolids)
      if len(newBases) == 0 :
         break
      else:
         Bases = newBases

    return simpleSolid,0

 
def SplitPlanes_org(Solids,UniverseBox):
    Bases = []
    icount=0
    err =0
    for sol in Solids :
      Bases.append((sol,[]))
       
# Loop  1 2 3   1 2 3  1 2 3   1 2 3   1 2 3  1 2 3
# order x y z   x z y  y x z   y z x   z x y  z y x
# index 0 0 0   0 1 0  1 0 0   1 1 0   2 0 0  2 1 0

    # planes orthogonal to X,Y,Z axis
    for iplane in range(3) :
       newBase =[]
       for item in Bases :
           
         base  = item[0]
         index = item[1]
         SPlanes = ExtractSurfaces(base,'Planes',UniverseBox,MakeObj=True)
         Planes = [SPlanes['PX'],SPlanes['PY'],SPlanes['PZ']]
         for i in index :
            del(Planes[i])
         pmin = len(Planes[0])
         imin = 0                
         for i in range(2-iplane): 
           if pmin > len(Planes[i+1]) :
             pmin = len(Planes[i+1])
             imin = i+1  
         
         if len(Planes[imin]) != 0:
            Tools = []
            for p in Planes[imin] :
              p.buildSurface() 
              Tools.append(p.shape)
            comsolid=BOPTools.SplitAPI.slice(base,Tools,'Split', tolerance = opt.splitTolerance)
            if len(comsolid.Solids) == 1 :
               if abs(comsolid.Solids[0].Volume-base.Volume)/base.Volume > tol.relativePrecision :
                  if opt.verbose : print('Warning. Part of the split object is missing original base is used instead',\
                        abs(comsolid.Solids[0].Volume-base.Volume)/base.Volume,comsolid.Solids[0].Volume,base.Volume)
                  base.exportStep('tmp_base.stp')
                  s = Part.Shape()
                  s.read('tmp_base.stp')                  
                  solid_list = s.Solids
                  err=1
               else:
                  solid_list = comsolid.Solids
            elif len(comsolid.Solids)> 1 :
               solid_list = comsolid.Solids
            else:
               solid_list = [base]  
         else :
            solid_list = [base]

         newindex = index[:]
         newindex.append(imin)
         for sol in solid_list:
           newBase.append((sol,newindex))
       Bases = newBase

    XYZBases = []
    for ii,s in enumerate(Bases):
      XYZBases.append(s[0])

    simpleSolid = []
    Bases = XYZBases

    # other planes
    while True: 
      newBases = []
      for base in Bases:
         cutSolids = splitPPlanes_org(base,UniverseBox)
         if len(cutSolids) == 1:
           simpleSolid.extend(cutSolids)
         else:
           newBases.extend(cutSolids)
      if len(newBases) == 0 :
         break
      else:
         Bases = newBases
    return simpleSolid,err


class parallelPlanes:
    def __init__(self,plane):
       self.Axis = plane.Surf.Axis
       self.elements = [plane]
       self.count = 1

    def append(self,plane):
       if isParallel(plane.Surf.Axis,self.Axis,0.1):
          self.elements.append(plane)
          self.count += 1
          return True
       else: 
          return False 

    def sort(self):
       pos=[]
       for i,p in enumerate(self.elements):
          d = self.Axis.dot(p.Surf.Position)  
          pos.append((d,i))
       pos.sort()

       sort = []
       for p in pos:
         sort.append(self.elements[p[1]])
       self.elements = sort

   
def sortPlanes(PlaneList,sortElements=False):
    if not PlaneList : return []
    newList = [parallelPlanes(PlaneList[0])]
    for p in PlaneList[1:]:
       found = False
       for pp in newList:
         if pp.append(p) : 
            found = True
            break
       if not found :
          newList.append(parallelPlanes(p)) 

    lenList = []
    for i,pp in enumerate(newList):
      if sortElements : pp.sort()
      lenList.append((pp.count,i))
    lenList.sort()

    sortedPlanes=[]
    for l in lenList:
       sortedPlanes.append(newList[l[1]].elements)
          
    return sortedPlanes
         
def splitPPlanes_new(solid,UniverseBox):
    SPlanes = ExtractSurfaces(solid,'Planes',UniverseBox)
  
    Planes = [] 
    for P in ('PX','PY','PZ','P'):
       Planes.extend(SPlanes[P])

    Planes = sortPlanes(Planes)
    
    if len(Planes) == 0 : return [solid]
    if len(Planes[-1]) < opt.nPlaneReverse : Planes.reverse()
    outSolid = [solid]
    for pp in Planes :
       for p in pp : p.buildSurface()
       tools = tuple( p.shape for p in pp) 
       comsolid=BOPTools.SplitAPI.slice(solid,tools,'Split',tolerance =opt.splitTolerance)         

       if len(comsolid.Solids) > 1 :
         outSolid = comsolid.Solids
         break
    return outSolid   
  
def splitPPlanes_org(solid,UniverseBox):
    SPlanes = ExtractSurfaces(solid,'Planes',UniverseBox)
    
    if len(SPlanes['P']) == 0 : return [solid]
    outSolid = [solid]
    for p in SPlanes['P'] :
       p.buildSurface()
       comsolid=BOPTools.SplitAPI.slice(solid,[p.shape],'Split', tolerance = opt.splitTolerance)
       if len(comsolid.Solids) > 1 :
         outSolid = comsolid.Solids
         break
    return outSolid   
        
def Split2ndOrder(Solids,UniverseBox):
    err = 0
    Base = Solids
    for kind in ['Cyl','Cone','Sph','Tor'] :                    
      kindBase = []
      while True :    
        cutBase=[]
        for solid in Base :
           Surfaces = ExtractSurfaces(solid,kind,UniverseBox)
           if len(Surfaces[kind]) == 0 :
              kindBase.append(solid)
           else:    
              simple = True
              for s in Surfaces[kind]:
                 s.buildSurface()
                 try :
                    comsolid=BOPTools.SplitAPI.slice(solid,[s.shape],'Split', tolerance = opt.splitTolerance)

                    solidsInCom = []
                    for s in comsolid.Solids :
                        if s.Volume > 1e-9 : solidsInCom.append(s)
                        
                    if len(solidsInCom) > 1 :
                       simple =False
                       cutBase.extend(solidsInCom)
                       break 
                 except :
                    if opt.verbose : print('Failed split base with {} surface'.format(s.shape.Faces[0].Surface))
                    err += 2

              if simple :         
                 kindBase.append(solid)

        if len(cutBase) == 0 :
           Base = kindBase
           break
        else:
           Base = cutBase

    return Base,err
    
def Split2ndOrderPlanes(Solids):
    err = 0
    simpleSolid = []
    Bases = Solids
    while True:
        newBases = []
        for base in Bases:
            cutSolids,err = split2ndOPlane(base)
            if len(cutSolids) == 1:
                simpleSolid.extend(cutSolids)
            else:
                newBases.extend(cutSolids)
        if len(newBases) == 0 :
            break
        else:
            Bases = newBases
         
    return simpleSolid,err
    
def split2ndOPlane(solid):

    Planes=[]
    err = 0
    flag_inv=CD.isInverted(solid)
    solid_GU=GU.solid_GU(solid)
    planes = Plane2ndOrder(solid_GU,None,flag_inv,convex=True)
    if not planes  : return [solid],err

    for p in planes:
      comsolid=BOPTools.SplitAPI.slice(solid,[p],'Split', tolerance = opt.splitTolerance)
      if not comsolid.Solids:
         comsolid=solid
         continue
      if len(comsolid.Solids) > 1 :
        removed, err = removeSolids(comsolid.Solids)
        comsolid=Part.makeCompound(removed)
        if len(removed) > 1: break

    return comsolid.Solids,err
    
def removeSolids(Solids):
    err = 0
    Vol_tol=1e-2
    Solids_Clean=[]
    for solid in Solids:
        if solid.Volume <= Vol_tol:
            if opt.verbose : print('Warning: degenerated solids are produced')
            err=2
            continue
        Solids_Clean.append(solid)
    
    return Solids_Clean,err
                        
def splitComponent(solidShape,UniverseBox):
    err  = 0
    err2 = 0

    Volini=solidShape.Volume
    Solids=solidShape.Solids
    # Split with explicit planes bounding the solid and
    # implicit planes interface of two 2nd order surfaces
    split0,err = SplitPlanes(Solids,UniverseBox,opt.newSplitPlane)
    # Split with explicit 2nd order surfaces bounding the solid
    
    split1,err1 = Split2ndOrder(split0,UniverseBox)
    err += err1

    split,err2  = Split2ndOrderPlanes(split1)
    err += err2
    Pieces = []
    for part in split:
       if part.Volume <= 1e-10:
          if opt.verbose : print('Warning: degenerated solids are produced')
          err += 2
          continue
       Pieces.append(part)                  
       
    comsolid=Part.makeCompound(Pieces)
 
    Volend=comsolid.Volume
    Volch=(Volini-Volend)/Volini
 
    if (abs(Volch)>1e-2): # 1% of Volume change
        if opt.verbose : print('Warning: Volume has changed after decomposition: %11.4E' %Volch)
        err += 4

    return comsolid, err
    
def SplitSolid(solidShape,UniverseBox):

    solidParts = []

    for solid in solidShape.Solids :
       
       explode = splitFullCylinder(solid)
       piece, err = splitComponent(explode,UniverseBox)
       solidParts.append(piece)
          
    
    return Part.makeCompound(solidParts) ,err   
    

src/GEOUNED/Decompose/Decom_one.py
GEOUNED-code/GEOUNED
import re
import FreeCAD
from GEOUNED.Utils.Functions import GEOUNED_Solid
from GEOUNED.Utils.Options.Classes import Options as opt


def GetLabel(label):
    """ Deleting the last word of the string if this is a number
        Only if the option delLastNumber is True"""
    if not opt.delLastNumber: return label
    wrd = label.split()
    try:
        val=float(wrd[-1])
        new_label = ' '.join(wrd[:-1])
        return new_label
    except: return label

def getCommentTree(obj):
    recursiveList = []
    cObj = obj
    while cObj.InList :
       label = GetLabel(cObj.InList[0].Label)
       recursiveList.append( label )
       cObj = cObj.InList[0]

    comment = ''
    for label in reversed(recursiveList):   
       comment += '/' + label
    return comment 

def joinEnvelopes(MetaList):
   joinList=[]
   oldID = -1
   newlist=[]
   for i,m in enumerate(MetaList):
      if m.CellType != 'envelope' : continue
      if m.EnclosureID != oldID:
         joinList.append(newlist)
         newlist = [i]
         oldID = m.EnclosureID
      else:   
         newlist.append(i)

   joinList.append(newlist)
   del joinList[0]

   for IDlist in joinList:
      fuseMetaObj(MetaList,IDlist[0],IDlist[-1]+1)
      
def fuseMetaObj(MetaList,init,end):
   if end-init == 1 :
      MetaList[init].__id__ = init+1
      return
   solids=[]
   for m in MetaList[init:end]:
      solids.extend(m.Solids)

   newMeta = GEOUNED_Solid(init+1,solids)
   newMeta.EnclosureID = MetaList[init].EnclosureID
   newMeta.ParentEnclosureID = MetaList[init].ParentEnclosureID
   newMeta.IsEnclosure = MetaList[init].IsEnclosure
   newMeta.CellType    = MetaList[init].CellType

   del MetaList[init:end]
   MetaList.insert(init,newMeta)
    

# Paco mod
def checkCADFileMaterial(Material, mdict):
    """Function to check that if a component in the CAD file has a material, it exist in the material file"""
    templist = [ elem for elem in set(Material) if elem not in mdict ]
    if templist:
        print('At least one material in the CAD model is not present in the material file')
        print('List of not present materials:', templist)
        print('Code STOPS')
        exit()
    return

# Paco mod
def setEnclosureSolidList(MetaList):
    """Function to define DataList list."""
    return [ elem for elem in MetaList if elem.IsEnclosure ]

def RemoveEnclosure(MetaList):
    """This function removes Enclosure solid and actualises __id__ attributes to take into account the removal from MetaList list of nested enclosures originally loaded from CAD model."""
    
    # update id of solids
    lenMetaList = len(MetaList)
    icount = 0
    for i, m in enumerate(MetaList):
        if m.IsEnclosure:
            icount += 1
        else:
            MetaList[i].__id__ -= icount
            
    # remove Enclosure solids from metaList
    for i in range(lenMetaList-1,-1,-1):
        if MetaList[i].IsEnclosure :
            MetaList.pop(i)
                    

def setEnclosureLevels(EnclosureList):
    nestedList      = [[0],[]]
    nestedEnclosure = [[]]
    parentLevel = 0
    tempList = EnclosureList[:]
    
    while len(tempList) > 0 :
       remove = [] 
       for i,encl in enumerate(tempList):
          if encl.ParentEnclosureID in nestedList[parentLevel] :
              nestedList[parentLevel+1].append(encl.EnclosureID)
              nestedEnclosure[parentLevel].append(encl)
              remove.append(i)
             
       nestedList.append([])
       nestedEnclosure.append([])
       parentLevel += 1
       remove.reverse()
       for i in remove:
          del tempList[i]

    nestedEnclosureList = []     
    for Level in nestedEnclosure :
       temp=[]
       for i,encl in enumerate(Level):
          temp.append((encl.ParentEnclosureID,i))
       temp.sort()

       grouped = []
       for t in temp:
          grouped.append(Level[t[1]])
       nestedEnclosureList.append(grouped)

    for i,Level in enumerate(nestedEnclosureList[0:-1]) :
        for encl in Level:
           childList = []
           for child in nestedEnclosureList[i+1]:
              if child.ParentEnclosureID == encl.EnclosureID :
                  childList.append(child)
           encl.SonEnclosures = childList[:]       
    
    return  nestedEnclosureList   


def checkEnclosure(FreeCAD_doc,EnclosureList):

    stop = False
    # check all enclosure labels have an associated solid
    TempList = []
    for elem in FreeCAD_doc.Objects:
       if (elem.TypeId=='Part::Feature'):
         if elem.Shape.Solids:
           if elem.InList:
              templabel = re.search('enclosure(?P<encl>[0-9]+)_(?P<parent>[0-9]+)_', elem.InList[0].Label)
              if templabel is not None:
                 if elem.TypeId == 'Part::Feature' and len(elem.Shape.Solids) == 0 :
                     TempList.append(elem)

    if TempList:
       stop = True
       print('One or more nested enclosure labels in CAD solid tree view/structure tree do not have any CAD solid.\n',\
              'Each nested enclosure must have only one solid.\nCode STOPS.',\
              '\nList of problematic nested enclosure labels:')
       for elem in TempList:
           print(elem.EnclosureID)
       
    # check enclosure Labels don't make loops

    # 1) check at leat one 0 is in Parent enclosure label
    # 2) check 0 is not in child enclosure label
    # 3) check child enclosure label is not repeated
    
    SIDList = []
    PIDSet  = set()
    repeatedID = set()

    for encl in EnclosureList:
       PIDSet.add(encl.ParentEnclosureID)
       if encl.EnclosureID in SIDList:
          repeatedID.add(encl.EnclosureID)
       else:       
          SIDList.append(encl.EnclosureID)

    if 0 in SIDList :
        stop = True
        print ('"0" cannot be label on child Enclosure')
    if 0 not in PIDSet :
        stop = True
        print (' "0" should parent label of most external enclosure(s)')
    if repeatedID:
        stop = True
        print('Child label cannot be repeated.\nRepeated labels :')
        for lab in repeatedID:
            print(lab)

    # this stop should not be move to the end of routine
    # if previous point not satisfied point 4) may lead to infite loop
    if stop : exit() 

    # 4) look for explicit loops
    enclDict = dict()
    for encl in EnclosureList:
        if encl.ParentEnclosureID in enclDict.keys():
            enclDict[encl.ParentEnclosureID].append(encl.EnclosureID)
        else:    
            enclDict[encl.ParentEnclosureID] = [encl.EnclosureID]

    parent = [0]
    cont = True
    while cont:
       cont = False 
       nextParent = [] 
       for p in parent:
          if p in enclDict.keys():
             cont = True
             nextParent.extend(enclDict[p])
             del enclDict[p]
       parent = nextParent   

    if enclDict.keys():
        print('Following enclosure produce loop')
        for p in enclDict.keys():
            for s in enclDict[p] :
                print ('{}_{}'.format(s,p))
        exit()

    # check enclosures solids are correctly nested and don't overlap each other
    nestedLevels = setEnclosureLevels(EnclosureList)

    overlap = []
    enclTree = [[0]]                   
    for level in nestedLevels :
        enclTree = updateTree(enclTree,level)               
        for encl in level:
           sameParent= dict()
           if encl.ParentEnclosureID in sameParent.keys():
               sameParent[encl.ParentEnclosureID].append(encl.EnclosureID)
           else:            
               sameParent[encl.ParentEnclosureID] = [encl.EnclosureID]
        for encl in sameParent.values():
            overlap.extend(checkOverlap(encl))

    notEmbedded = []
    for chain in enclTree :
        up = chain[0]               
        for low in chain[1:]:
           inter = up.checkIntersection(low.CADSolid)
           if inter != -2 :
             notEmbedded.append((low.EnclosureID,up.EnclosureID))          

    if notEmbedded :
       stop = True
       print ( ' Following enclosures are not fully embedded in Parent enclosure')
       for elemt in notEmbedded:
           print('{}_{}').format(elemt[0],elemt[1])
                       
    if overlap :
       stop = True
       print ( ' Following enclosures overlapping ')
       for elemt in overlap:
           print('{}_{}').format(elemt[0],elemt[1])
 
    if stop : exit()
                       
def checkOverlap(enclosures):
     overlap = []
     for i,enc1 in enumerate(enclosures):
        for enc2 in enclosures[i+1:]:
           inter = enc1.checkIntersection(enc2.CADSolid)
           if inter != 1:
              overlap.append((enc1.EnclosureID,enc2.EnclosureID))
     return overlap                  

def updateTree(Tree,level):
    newTree = []
    for encl in level :
       for lst in Tree:
           if lst[-1] == encl.ParentEnclosureID :
               newTree.append(lst + [encl.EnclosureID])
               continue
    return newTree                  

def set_docOptions():
    # set import step document options for FreeCAD version >0.18 compatible with FreeCAD0.18 opening options
    p0 = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Import")
    p0.SetBool("UseLinkGroup",False)
    p0.SetBool("ReduceObjects",False)
    p0.SetBool("ExpandCompound",False)
    
    p1 = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Import/hSTEP")
    p1.SetBool("UseLinkGroup",False)
    p1.SetBool("ReadShapeCompoundMode",True)
    

# functions not used in the code                       
def checkIndex(docList,index,returnObject=False):
    subList = docList.ElementList
    for i in index[:-1]:
        if len(subList) > i :
           subList = subList[i]
           if subList.TypeId != 'App::LinkGroup'   :  return None
           subList=subList.ElementList
        else:
           return None

    if len(subList) > index[-1] :
       if subList[index[-1]].TypeId == 'App::LinkGroup'  :  
          return 'Link'
       else:
          if returnObject :
             return subList[ index[-1]]
          else: 
             return 'Feature'       
    else:
       return None    
       
def nextIndex(docList,lastIndex=None):
    if lastIndex is None:
       lastIndex = [0]
       while checkIndex(docList,lastIndex) != 'Feature' :
           lastIndex.append(0)    
       else:
           return lastIndex         

    tmp = lastIndex[:]
    move = True
    while True:
       if move:
          tmp[-1] = tmp[-1]+1
       obj = checkIndex(docList,tmp)    
       if obj == 'Feature' :
          return tmp
       elif  obj == 'Link'  : 
          tmp.append(0)
          move = False
       else:
          tmp =tmp[0:-1]
          move = True
          if len(tmp) == 0 : return None


def solidGenerator(doclist) :
   last = None
   while True:
      last = nextIndex(doclist,last)
      if last is None : break
      yield checkIndex(doclist,last,True)

src/GEOUNED/LoadFile/LoadFunctions.py
GEOUNED-code/GEOUNED
#
# Module to load a STEP file
#
import FreeCAD, Part
from FreeCAD import Import
import re
import os.path
import GEOUNED.Utils.Functions as UF
import GEOUNED.LoadFile.LoadFunctions as LF


# Paco mod
def extractMaterials(filename):
    rhoreal = []
    namestr = []
    mdict = {} #_ Material dictionary
    with open(filename,"rt") as file:
      for line in file:
        vals = line.split()
        if vals[0].startswith("#"): continue
        matlabel = int(vals[0])
        rhoreal  = -float(vals[1])
        matname  = " ".join(vals[2:])
        mdict[matlabel] = (rhoreal,matname)
    return mdict

def LoadCAD(filename,matfilename,defaultMat=[],compSolids=True):

    # Set document solid tree options when opening CAD differing from version 0.18
    if int(FreeCAD.Version()[1]) > 18 : LF.set_docOptions()

    CAD_simplificado_doc=FreeCAD.newDocument("CAD_simplificado")
    Import.insert(filename,"CAD_simplificado")

    if matfilename != '' :
       if os.path.exists(matfilename): 
          mdict = extractMaterials(matfilename)
       else:
          print('Material definition file {} does not exist.'.format(matfilename))
          mdict = {} 
    else:
       mdict = {}
 
    s=Part.Shape()
    s.read(filename)
    Solids=s.Solids
    MetaList = []
    for i,s in enumerate(Solids) :
      MetaList.append(UF.GEOUNED_Solid(i+1,s))

    isolid = 0
    missingMat = set()

    docObjects = CAD_simplificado_doc.Objects

    for elem in docObjects:
        if (elem.TypeId  == 'Part::Feature'):
           comment = LF.getCommentTree(elem)
           if not elem.Shape.Solids:
              print('Warning: Element {:} has no associated solid'.format(comment+'/'+elem.Label))
              continue
           else: 
              tempremat = None
              tempredil = None
            
              # MIO: lightly modification of label if required
              label = LF.GetLabel(elem.Label)
              comment=comment+'/'+label
              if elem.InList:
                 # MIO: lightly modification of label if required
                 label_inList = LF.GetLabel(elem.InList[0].Label)
                 enclLabel  = re.search('enclosure(?P<encl>[0-9]+)_(?P<parent>[0-9]+)_', label_inList)
                 if not enclLabel :
                     enclLabel  = re.search('enclosure(?P<encl>[0-9]+)_(?P<parent>[0-9]+)_', label)
                     
                 envelLabel = re.search('envelope(?P<env>[0-9]+)_(?P<parent>[0-9]+)_', label_inList)
                 if not envelLabel :
                     envelLabel = re.search('envelope(?P<env>[0-9]+)_(?P<parent>[0-9]+)_', label)

                 #tempremat = re.search("(m(?P<mat>\d+)_)",elem.Label)
                 #if not tempremat :
                 #    tempremat = re.search("(m(?P<mat>\d+)_)",elem.InList[0].Label)

                 #tempredil = re.search("(_d(?P<dil>\d*\.\d*)_)",elem.Label)
                 #if not tempredil :
                 #    tempredil = re.search("(_d(?P<dil>\d*\.\d*)_)",elem.InList[0].Label)
                     
                 # Paco modifications
                 # Search for material definition in tree
                 xelem = [elem]
                 while xelem and not tempremat:
                    # MIO: Modification of label if required
                    temp_label = LF.GetLabel(xelem[0].Label)
                    tempremat = re.search("_m(?P<mat>\d+)_","_"+temp_label )
                    xelem = xelem[0].InList

                 # Search for dilution definition in tree
                 xelem = [elem]
                 while xelem and not tempredil:
                    # MIO: Modification of label if required
                    temp_label = LF.GetLabel(xelem[0].Label)
                    tempredil = re.search("_d(?P<dil>\d*\.\d*)_",temp_label)
                    xelem = xelem[0].InList
                 # Paco end
              else:
                 enclLabel   = None
                 envelLabel  = None

            # compSolid Diferent solid of the same cell are stored in the same metaObject (compSolid)
            # enclosures and envelopes are always stored as compound
              if  compSolids or enclLabel or envelLabel :
                 
                 init = isolid
                 end  = isolid+len(elem.Shape.Solids)
                 LF.fuseMetaObj(MetaList,init,end)
                 nsolids = 1
              else:
                 nsolids = len(elem.Shape.Solids)
              
              for i in range(nsolids):
                  MetaList[isolid].setComments('{}{}'.format(comment,(i+1)))
                  MetaList[isolid].setCADSolid()
                
                  if tempremat:
                      matlabel = int(tempremat.group('mat'))
                      if matlabel in mdict.keys() :
                         MetaList[isolid].setMaterial(matlabel,mdict[matlabel][0],mdict[matlabel][1])
                      else:
                         if matlabel == 0 :
                            MetaList[isolid].setMaterial(matlabel,0,0)
                         else:
                            MetaList[isolid].setMaterial(matlabel,-100,'Missing material density information')
                            missingMat.add(matlabel)          
                  else:
                     #print('Warning : No material label associated to solid {}.\nDefault material used instead.'.format(comment)) 
                     if defaultMat:
                         MetaList[isolid].setMaterial(*defaultMat)
                  if tempredil:
                      MetaList[isolid].setDilution(float(tempredil.group('dil'))) 
 
                  if enclLabel is not None:
                      MetaList[isolid].EnclosureID = int(enclLabel.group('encl'))
                      MetaList[isolid].ParentEnclosureID = int(enclLabel.group('parent'))
                      MetaList[isolid].IsEnclosure = True
                      MetaList[isolid].CellType    = 'void'
                       
                  if envelLabel is not None:
                      MetaList[isolid].EnclosureID = int(envelLabel.group('env'))
                      MetaList[isolid].ParentEnclosureID = int(envelLabel.group('parent'))
                      MetaList[isolid].IsEnclosure = True
                      MetaList[isolid].CellType    = 'envelope'  
                  isolid += 1     

    LF.joinEnvelopes(MetaList)
    if missingMat :
        print('Warning!! At least one material in the CAD model is not present in the material file')
        print('List of not present materials:', missingMat)

    EnclosureList = LF.setEnclosureSolidList(MetaList)
    if EnclosureList:
        LF.checkEnclosure(CAD_simplificado_doc,EnclosureList)
        # LF.RemoveEnclosure(MetaList)
        return MetaList,EnclosureList
    else:
        return MetaList,[]











src/GEOUNED/LoadFile/LoadSTEP.py
GEOUNED-code/GEOUNED
#
# Set of useful functions used in different parts of the code
#
import math

def isSameValue(v1,v2,tolerance=1e-6):
    return abs(v1-v2) < tolerance 


def isOposite(Vector1,Vector2,tolerance=1e-6):
    return abs(Vector1.getAngle(-Vector2)) < tolerance
        

def isParallel(Vector1,Vector2,tolerance=1e-6):
    angle = abs(Vector1.getAngle(Vector2))
    return (angle < tolerance or isSameValue(angle,math.pi,tolerance) ) 


def isInLine(Point,Dir,Pnt_line,tolerance=1e-6):
    r12 = Point - Pnt_line
    return (isParallel(Dir,r12) or (r12.Length < tolerance))      

    
def isInPoints(point,Points,tolerance=1e-5):
   if len(Points)>0:
        for P in Points:
            if point.isEqual(P,tolerance):
                return True
   return False

def isInEdge(Edge1,Edge2,tolerance=1e-8):
    Ver1=Edge1.Vertexes
    Ver2=Edge2.Vertexes
    con1 = Ver1[0].Point.isEqual(Ver2[0].Point,tolerance) or Ver1[0].Point.isEqual(Ver2[1].Point,tolerance)
    con2 = Ver1[1].Point.isEqual(Ver2[0].Point,tolerance) or Ver1[1].Point.isEqual(Ver2[1].Point,tolerance)    
    return  (con1 and con2)


def isInPlane(Point,plane,dtolerance=1e-7):
    return abs(Point.distanceToPlane(plane.Surf.Position,plane.Surf.Axis)) < dtolerance


def isInTolerance(val,tol,fuzzyLow,fuzzyHigh):
    if abs(val) < fuzzyLow :
        return True,False   # 1) isintolerance 2) fuzzy
    elif abs(val) < tol :
        return True,True
    elif abs(val) > fuzzyHigh :
        return False,False
    else:
        return False,True

def signPlane(Point,plane):
    value = plane.Surf.Axis.dot(Point-plane.Surf.Position) 
    if (value>=0.0):
        sign=1
    else:
        sign=-1
    return sign

class PlaneParams:
   def __init__(self,params,real=True):
      self.Position = params[0] 
      self.Axis     = params[1]
      self.dimL     = params[2]
      self.solidID  = params[3]
      self.real     = real
      

   def __str__(self):
      outstr = '''Plane :
    Axis     : {}  {}  {} 
    Position : {}  {}  {}'''.format(self.Axis.x    ,self.Axis.y    ,self.Axis.z,\
                                    self.Position.x,self.Position.y,self.Position.z)
      return outstr

class CylinderParams:
   def __init__(self,params,real=True):
      self.Center = params[0] 
      self.Axis   = params[1]
      self.Radius = params[2]
      self.dimL   = params[3]
      self.real   = real

   def __str__(self):
      outstr = '''Cylinder :
    Axis     : {}  {}  {} 
    Center   : {}  {}  {}
    Radius   : {}  '''.format(self.Axis.x    ,self.Axis.y    ,self.Axis.z,\
                             self.Center.x,self.Center.y,self.Center.z,\
                              self.Radius)
      return outstr

class ConeParams:
   def __init__(self,params,real=True):
      self.Apex   = params[0] 
      self.Axis   = params[1]
      self.SemiAngle = params[2]
      self.dimL   = params[3]
      self.dimR   = params[4]
      self.real   = real

   def __str__(self):
      outstr = '''Cone :
    Axis     : {}  {}  {} 
    Center   : {}  {}  {}
    SemiAngle: {}  '''.format(self.Axis.x    ,self.Axis.y    ,self.Axis.z,\
                             self.Apex.x,self.Apex.y,self.Apex.z,\
                              self.SemiAngle)
      return outstr


class SphereParams:
   def __init__(self,params):
      self.Center = params[0] 
      self.Radius = params[1]

   def __str__(self):
      outstr = '''Sphere :
    Center   : {}  {}  {}
    Radius   : {}  '''.format(self.Center.x,self.Center.y,self.Center.z,\
                              self.Radius)
      return outstr

class TorusParams:
    def __init__(self,params):
      self.Center      = params[0]
      self.Axis        = params[1]
      self.MajorRadius = params[2]
      self.MinorRadius = params[3]

    def __str__(self):
      outstr = '''Torus :
    Axis     : {}  {}  {} 
    Center   : {}  {}  {}
    MajorRadius: {}
    MinorRadius: {} '''.format(self.Axis.x    ,self.Axis.y    ,self.Axis.z,\
                              self.Center.x,self.Center.y,self.Center.z,\
                              self.MajorRadius,self.MinorRadius)
      return outstr
       

src/GEOUNED/Utils/BasicFunctions_part1.py
GEOUNED-code/GEOUNED
#
# Set of useful functions used in different parts of the code
#
from GEOUNED.Utils.BasicFunctions_part1 \
     import isParallel, isSameValue, isInTolerance
from GEOUNED.Write.Functions import MCNPSurface
import FreeCAD
import math


sameSurfFic = open('fuzzySurfaces','w')
def Fuzzy(index,dtype,surf1,surf2,val):

    if dtype == 'plane':
       p1str = MCNPSurface(index,'Plane',surf1)
       p2str = MCNPSurface(0,'Plane',surf2)
       line = 'Plane distance : {} \n {}\n {}\n\n'.format(val,p1str,p2str) 
       sameSurfFic.write(line) 

    elif dtype == 'cylRad':
      cyl1str = MCNPSurface(index,'Cylinder',surf1)
      cyl2str = MCNPSurface(0,'Cylinder',surf2)
      line = 'Diff Radius: {} \n {}\n {}\n\n'.format(val,cyl1str,cyl2str) 
      sameSurfFic.write(line)
      
    elif dtype == 'cylAxs':
      cyl1str = MCNPSurface(index,'Cylinder',surf1)
      cyl2str = MCNPSurface(0,'Cylinder',surf2)
      c12 = surf1.Center - surf2.Center
      line = 'Dist Axis: {} \n {}\n {}\n\n'.format(val,cyl1str,cyl2str)  
      sameSurfFic.write(line)
      
    
def isSamePlane(p1,p2,dtol=1e-6,atol=1e-6,relTol=True,fuzzy=(False,0)):     
    if isParallel(p1.Axis,p2.Axis,atol):
      c12 = p1.Position - p2.Position
      d = abs(p1.Axis.dot(c12))
      if relTol :
          tol = dtol * max(p2.dim1,p2.dim2)
      else :
          tol = dtol

      isSame,isFuzzy = isInTolerance(d,tol,0.5*tol,2*tol)   
      if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'plane',p2,p1,d)

      return isSame 
    return False     

def isSameCylinder(cyl1,cyl2,dtol=1e-6, atol=1e-6, relTol=True, fuzzy=(False,0)):
    if relTol :
       rtol = dtol * max(cyl2.Radius,cyl1.Radius)
    else:
       rtol = dtol
       
    isSameRad,isFuzzy = isInTolerance(cyl2.Radius-cyl1.Radius,rtol,0.5*rtol,2*rtol)   
    if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylRad',cyl2,cyl1,abs(cyl2.Radius-cyl1.Radius)) 

    if isSameRad :
       if isParallel(cyl1.Axis,cyl2.Axis,atol):
          c12 = cyl1.Center-cyl2.Center
          d = cyl1.Axis.cross(c12).Length
          
          if relTol:
             tol = dtol * max(cyl1.Center.Length,cyl2.Center.Length)
          else:
             tol = dtol
          
          isSameCenter,isFuzzy = isInTolerance(d,tol,0.5*tol,2*tol) 
          if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylAxs',cyl1,cyl2,d) 
    
          return isSameCenter  
    return False


def isSameCone (cone1,cone2, dtol=1e-6, atol=1e-6, relTol=True):
    if isSameValue(cone1.SemiAngle,cone2.SemiAngle,atol) :
       if isParallel(cone1.Axis,cone2.Axis,atol) :
          if relTol :
             tol = dtol * max(cone1.Apex.Length,cone2.Apex.Length) 
          else:
             tol = dtol 
          return cone1.Apex.isEqual(cone2.Apex,tol) 
    return False  
    

def isSameSphere (sph1,sph2,tolerance=1e-6,relTol=True):
    if relTol :
       rtol = tolerance * max(sph2.Radius,sph1.Radius)
    else:
       rtol = tolerance 
    if isSameValue(sph1.Radius, sph2.Radius, rtol):
       if relTol :
          ctol = tolerance * max(sph2.Center.Length,sph1.Center.Length)
       else:
          ctol = tolerance 
       return sph1.Center.isEqual(sph2.Center,ctol) 

    return False


def isSameTorus (tor1,tor2,dtol=1e-6,atol=1e-6,relTol=True):
    if isParallel(tor1.Axis,tor2.Axis,atol) :
       if relTol :
          Rtol = dtol * max(tor1.MajorRadius, tor2.MajorRadius)
          rtol = dtol * max(tor1.MinorRadius, tor2.MinorRadius)
       else:
          Rtol = dtol 
          rtol = dtol 

       if isSameValue(tor1.MajorRadius, tor2.MajorRadius, Rtol) and \
          isSameValue(tor1.MinorRadius, tor2.MinorRadius, rtol)  :
          if relTol :
             ctol = dtol * max(tor1.Center.Length, tor2.Center.Length)
          else:
             ctol = dtol
          return tor1.Center.isEqual(tor2.Center,ctol) 
    return False

def isDuplicateInList(num_str1,i,lista):
    for j,elem2 in enumerate(lista):
        if (i == j ) : continue
        num_str2='%11.4E' %(elem2)
        num_str3='%11.4E' %(elem2+2.0*math.pi)
        num_str4='%11.4E' %(elem2-2.0*math.pi)

        if abs(float(num_str2))<1.0e-5:
            num_str2='%11.4E' %0.0
            
        if abs(float(num_str3))<1.0e-5:
            num_str3='%11.4E' %0.0
        
        if abs(float(num_str4))<1.0e-5:
            num_str4='%11.4E' %0.0
        
        if (num_str1 == num_str2 or num_str1 == num_str3 or num_str1 == num_str4):
            return True

    return False
    
def isInFaces(face,Faces):

    if (Faces==[]):
        return False
    vector_nulo=FreeCAD.Vector(0,0,0)
    surface=face.Surface
    kind_surf=str(face.Surface)
    if kind_surf=='<Plane object>':
        Axis=surface.Axis
        Position=surface.Position

    elif kind_surf=='<Cylinder object>':
        Axis=surface.Axis
        Radius=surface.Radius
        Center=surface.Center

    elif kind_surf=='<Cone object>':
        Axis=surface.Axis
        Apex=surface.Apex
        SemiAngle=surface.SemiAngle
    
    elif kind_surf[0:6] == 'Sphere':
        Center=surface.Center
        Radius=surface.Radius

    elif kind_surf=='<Toroid object>':
        Axis        =surface.Axis
        Center      =surface.Center
        MajorRadius =surface.MajorRadius
        MinorRadius =surface.MinorRadius
        
    for elem in Faces:
        surf=elem.Surface
        ##print surf
        if (str(surf) == '<Plane object>' and kind_surf == '<Plane object>'):
            vector_cross=Axis.cross(surf.Axis)
            if (vector_cross == vector_nulo and isInPlane(Position,surf)):
                return True
        elif (str(surf) == '<Cylinder object>' and kind_surf == '<Cylinder object>'):
            dir = surf.Axis
            rad = surf.Radius
            pnt = surf.Center
            vector_cross=Axis.cross(surf.Axis)
            if (vector_cross == vector_nulo and Radius == rad and isInLine(Center,dir,pnt)):
                return True
        elif (str(surf) == '<Cone object>' and kind_surf =='<Cone object>'):
            # corresponding logic for cone
            dir = surf.Axis
            punta = surf.Apex
            semiangle=surf.SemiAngle
            if (Axis.isEqual(dir,1e-5) and Apex.isEqual(punta,1e-5) and (SemiAngle-semiangle)<1e-6):
                return True
        elif (str(surf)[0:6] == 'Sphere' and kind_surf[0:6] == 'Sphere'):
            # corresponding logic for sphere
            rad = surf.Radius
            pnt = surf.Center
            if (Center == pnt and Radius == rad):
                return True

        elif (str(surf) == '<Toroid object>' and kind_surf =='<Toroid object>'):
            # corresponding logic for Torus
            radMaj = surf.MajorRadius
            radMin = surf.MinorRadius
            pnt    = surf.Center
            dir    = surf.Axis
            if (Axis.isEqual(dir,1e-5)    and Center.isEqual(pnt,1e-5) and
               (MajorRadius-radMaj)<1e-6) and(MinorRadius-radMin)<1e-6 :
                return True
    
    return False

def isInFaces2(face,Faces):

    if (Faces==[]):
        return False
    vector_nulo=FreeCAD.Vector(0,0,0)
    surface=face
    kind_surf=face.type
    if kind_surf=='<Plane object>':
        Axis=surface.Axis
        Position=surface.Position
        
    elif kind_surf=='<Cylinder object>':
        Axis=surface.Axis
        Radius=surface.Radius
        Center=surface.Center
        
    elif kind_surf=='<Cone object>':
        Axis=surface.Axis
        Apex=surface.Apex
        SemiAngle=surface.SemiAngle
    
    elif kind_surf[0:6] == 'Sphere':
        Center=surface.Center
        Radius=surface.Radius

    elif kind_surf=='<Toroid object>':
        Axis        =surface.Axis
        Center      =surface.Center
        MajorRadius =surface.MajorRadius
        MinorRadius =surface.MinorRadius
        
    for elem in Faces:
        ##print surf
        if (elem.type == '<Plane object>' and kind_surf == '<Plane object>'):
            vector_cross=Axis.cross(elem.Axis)
            if (vector_cross == vector_nulo and isInPlane(Position,elem.Surface)):
            #if (isParallel(elem.Axis,elem.Surface.Axis) and isInPlane(Position,elem.Surface)):
                return True
        elif (elem.type == '<Cylinder object>' and kind_surf == '<Cylinder object>'):
            dir = elem.Axis
            rad = elem.Radius
            pnt = elem.Center
            vector_cross=Axis.cross(elem.Axis)
            if (vector_cross == vector_nulo and Radius == rad and isInLine(Center,dir,pnt)):
                return True
        elif (elem.type == '<Cone object>' and kind_surf =='<Cone object>'):
            # corresponding logic for cone
            dir = elem.Axis
            punta = elem.Apex
            semiangle=elem.SemiAngle
            if (Axis.isEqual(dir,1e-5) and Apex.isEqual(punta,1e-5) and (SemiAngle-semiangle)<1e-6):
                return True
        elif (elem.type == 'Sphere' and kind_surf == 'Sphere'):
            # corresponding logic for sphere
            rad = elem.Radius
            pnt = elem.Center
            if (Center == pnt and Radius == rad):
                return True
        elif (elem.type == '<Toroid object>' and kind_surf =='<Toroid object>'):
            # corresponding logic for Torus
            radMaj = elem.MajorRadius
            radMin = elem.MinorRadius
            pnt    = elem.Center
            dir    = elem.Axis
            if (Axis.isEqual(dir,1e-5)    and Center.isEqual(pnt,1e-5) and
               (MajorRadius-radMaj)<1e-6) and(MinorRadius-radMin)<1e-6 :
                return True    
    return False


src/GEOUNED/Utils/BasicFunctions_part2.py
GEOUNED-code/GEOUNED
#
#   Conversion to MCNP v0.0 
#   Only one solid and planar surfaces
#
import FreeCAD
import BOPTools.SplitAPI
import math
import re
from GEOUNED.Utils.Functions import GEOUNED_Surface
from GEOUNED.Utils.booleanFunction import BoolSequence

BoolVals = (None,True,False)

class CTelement:
   def __init__(self,val=None,S1=None,S2=None):
      self.diagonal = False
      self.S1       = S1
      self.S2       = S2
      if val is None :
         self.val = None
      else:
         if type(val) is int : 
            self.diagonal = True
            self.type  = None
         else:
            self.type  = val.count(0)
         self.val = val

   def getTranspose(self):
      if self.diagonal : return self.val
      return  CTelement((self.val[0],self.val[3],self.val[2],self.val[1]),self.S2,self.S1) 

   def getDependence(self):
      if self.diagonal : 
         if   self.val ==  0  : return True,False
         elif self.val == -1  : return 'Null',False
         else                 : return True,'Null'

      Ones = sum(self.val)
      if Ones == 4   : return None,None
      elif Ones == 3  :
         ind = self.val.index(0)
         if   ind == 0 : return False,None
         elif ind == 1 : return True ,None
         elif ind == 2 : return None ,True
         else          : return None ,False
      elif Ones == 2  :
         ind1 = self.val.index(0)
         ind2 = self.val.index(0,ind1+1)
         if   ind1 == 0 and ind2 == 1 : return 'Null',None
         elif ind1 == 2 and ind2 == 3 : return  None ,'Null'
         elif ind1 == 0 and ind2 == 3 : return  False,False
         else                         : return  True ,True
      elif Ones == 1  :  
         ind = self.val.index(1)
         if   ind == 0 : return True ,'Null'
         elif ind == 1 : return False,'Null'
         elif ind == 2 : return 'Null',False
         else          : return 'Null',True 
      else:
         return 'Null','Null'
          

class ConstraintTable(dict):

   def __init__(self):
       self.diagonal = None

   def __str__(self):

      varName = list(self.keys())

      # Constraint Table only diagonal terms
      if len(self[varName[0]]) == 1 :
          line = ''
          for name in varName :
             element = self[name][name]
             line += ' {:4d} : {}\n'.format(name,element.val)
          return line
           
      outstr = '  '
      for name in varName :
          outstr = outstr + ' {:3d}'.format(name)
      outstr = outstr + '\n'    

      for name1 in varName :
          line       = ' {:3d} '.format(name1)
          linenot    = '~{:3d} '.format(name1)
          for name2 in varName:
             elmt = self[name1][name2]  
             if elmt.diagonal :
               line    += ' {:>2d} '.format(elmt.val)
               linenot += '    '
             else:
               line    += ' {}{} '.format(elmt.val[0],elmt.val[1])
               linenot += ' {}{} '.format(elmt.val[3],elmt.val[2])
          outstr += line   +'\n'
          outstr += linenot+'\n'
      return outstr   

   def addElement(self,k1,k2,val):

       if k1 in self.keys(): 
          self[k1][k2] = val
       else:
          self[k1] = {k2:val}  

   def fillMissingElements(self):
      keys = list(self.keys())
      missing = []
      for i,k1 in enumerate(keys):
         for k2 in keys[i+1:]:
             if k2 in self[k1].keys():
                elmt = self[k1][k2]
                self[k2][k1] = elmt.getTranspose()
             else:
                missing.append((k1,k2))

      
      for k1,k2 in missing:
         diag1 = self[k1][k1]
         diag2 = self[k2][k2]
         new = combineDiagElements (diag1,diag2)
         new.S1 = k1
         new.S2 = k2
         self[k1][k2] = new
         self[k2][k1] = new.getTranspose()

   def getOutSurfaces(self):
      out = [] 
      for k in self.keys():
         if self[k][k].val != 0 :  out.append(k)
      return out       
     
   def getConstraintSet(self,valname):
       trueSet  = {}
       falseSet = {}
       TNull    = False
       FNull    = False
       for k in self.keys():
          TValue,FValue = self[valname][k].getDependence()
          if TValue == 'Null' :
               TNull = True
               TValue = None
          if FValue == 'Null' :
               FNull = True
               FValue = None
          if TValue is not None  : trueSet[k]  = TValue
          if FValue is not None  : falseSet[k] = FValue

       if TNull : trueSet  = None
       if FNull : falseSet = None
       return trueSet,falseSet 
     
   def solidInBox(self,Seq):             #  Sequence of the cell
      surfs = Seq.getSurfacesNumbers()
      if self.diagonal :
         seqValues = dict()
         for s in surfs : 
            seqValues[s] = BoolVals[self[s][s].val]
         # if evaluate   None  : Box intersection Cell != 0      Part of the cell in the box
         #               True  : Box intersection Cell == Box    Cell cover the full region of the box. Void cell doesn't exist
         #               False : Box intersection Cell == 0      Cell out of the box 
         return Seq.evaluate(seqValues)

      else:
         trueSet,falseSet = self.getConstraintSet(surfs[0])
         if trueSet is not None:  
             trueVal  = Seq.evaluate(trueSet) 
             if trueVal is None : return None
             if falseSet is not None:  
                falseVal = Seq.evaluate(falseSet) 
                if falseVal is None    : return None     # Part of the cell in the box
                if trueVal == falseVal : return trueVal  # True Cover full cell, False not in the box 
                else                   : return None     # Part of the cell in the box                
             else: 
                return trueVal
         elif falseSet is not None:  
             falseVal = Seq.evaluate(falseSet) 
             return falseVal
         else:
            print('Bad trouble surfaces {} is on none side of the box!!')
            return False       


def combineDiagElements(d1,d2):
    
    if   d1.val == 0 and d2.val == 0 : return CTelement((1,1,1,1))
    elif d1.val == 1 and d2.val == 0 : return CTelement((1,1,0,0))
    elif d1.val ==-1 and d2.val == 0 : return CTelement((0,0,1,1))
    elif d1.val == 0 and d2.val == 1 : return CTelement((1,0,0,1))
    elif d1.val == 0 and d2.val ==-1 : return CTelement((0,1,1,0))
    elif d1.val == 1 and d2.val == 1 : return CTelement((1,0,0,0))
    elif d1.val == 1 and d2.val ==-1 : return CTelement((0,1,0,0))
    elif d1.val ==-1 and d2.val == 1 : return CTelement((0,0,0,1))
    elif d1.val ==-1 and d2.val ==-1 : return CTelement((0,0,1,0))
      
def buildCTableFromSolids(Box,SurfInfo,option='diag'):

     if type(SurfInfo) is dict :
         surfaces = SurfInfo
         surfaceList = tuple(surfaces.keys())
     elif type(SurfInfo) is tuple :
         surfaceList,surfaces = SurfInfo
     else:
         surfaces    = SurfInfo.Surfaces
         surfaceList = SurfInfo.surfaceList

     outSurfaces=[]
     if type(surfaces[surfaceList[0]]) is GEOUNED_Surface :
        for s in surfaceList: 
           ss = surfaces[s]
           ss.__boundBox__ = Box.BoundBox
           ss.buildSurface()
     else:
        for s in surfaceList: surfaces[s].buildShape(Box.BoundBox)

     CTable =  ConstraintTable()
     if option == 'diag':
        CTable.diagonal = True
     else:
        CTable.diagonal = False

     for i,s1 in enumerate(surfaceList):
         res,splitRegions = splitSolid_fast(Box,surfaces[s1],True)
         #res,splitRegions = splitSolid_fast(Box,Surfaces.getSurface(s1),True)
                    
         CTable.addElement(s1,s1,CTelement(res,s1,s1))
         if option == 'diag' :continue
         if splitRegions is None : continue  # loop, no region to be split by s2
         for j,s2 in enumerate(surfaceList[i+1:]) :
            posS1,negS1 = splitRegions

            pos0 = None
            for solid in posS1 :

                pos = splitSolid_fast(solid,surfaces[s2],False)
                    
                #pos = splitSolid_fast(solid,Surfaces.getSurface(s2),False)
                if pos == (1,1) : break   # s2 intersect S1 Region
                if pos0 is None :
                   pos0 = pos
                else:
                   if pos != pos0 :       # s1 regions are on both side of s2
                      pos = (1,1)
                      break
                   
            neg0 = None
            for solid in negS1 :
                #neg = splitSolid_fast(solid,Surfaces.getSurface(s2),False)
                neg = splitSolid_fast(solid,surfaces[s2],False)
                if neg == (1,1) : break   # s2 intersect S1 Region
                if neg0 is None :
                   neg0 = neg
                else:
                   if neg != neg0 :       # s1 regions are on both side of s2
                      neg = (1,1)
                      break       

            val = (pos[0],pos[1],neg[1],neg[0])
            CTable.addElement(s1,s2,CTelement(val,s1,s2))  


     # if some surfaces don't cross the box some elements in Constraint table are not filled
     if option != 'diag' : CTable.fillMissingElements()
     return CTable


def removeExtraSurfaces(CellSeq,CTable):
    # checking is make on solid cell definition to be removed from void cell
    outSurfaces = set(CTable.getOutSurfaces())
    changed = False
    newDef = BoolSequence(operator='OR')
  
    #Loop over all compound solids of the metaSolid
    if  CellSeq.level == 0 : 
        newSeq = BoolSequence(operator='AND')
        newSeq.append(CellSeq.copy())
        CellSeq.assign(newSeq)

    for subCell in CellSeq.elements:
        nullcell = False 
        chk = subCell.check()

        if chk == False :    # the cell doesn't exist
           nullcell = True
        elif chk == True :   # the cell describe the full universe
           newDef.elements = True
           newDef.level = 0
           return  newDef
       
        #if subcell has finite volume check it intersection with the box        
        if not nullcell :
           res = CTable.solidInBox(subCell)
           if res == None :   
                # subcell intersect the box
                # get the surfaces of the solids out of the box
                # get reduced definition
                removeSurf = outSurfaces & set(subCell.getSurfacesNumbers())
                for s in removeSurf:
                   val = True if CTable[s][s].val > 0 else False
                   subCell.substitute(s,val)
                if type(subCell.elements) is bool : 
                   if subCell.elements == False :   #  cell does not intersect void box
                      continue
                   else:                            # cell cover fully void box
                      newDef.elements = True
                      newDef.level = 0
                      return  newDef
                else:
                   newDef.append(subCell)

           elif res == True :
              # subcell cover the full box region Void cell doesn't exist
              newDef.elements = True
              newDef.level = 0
              return  newDef

    newDef.clean()

    return newDef      


def splitSolid_fast(solid,surf,box):

    if box :
       if surf.shape :
            comsolid = BOPTools.SplitAPI.slice(solid,[surf.shape],'Split', tolerance = 0)
       else:
            return  checkSign(solid,surf),None

       if len(comsolid.Solids) <= 1 :
          return checkSign(solid,surf),None
         # sgn = checkSign(solid,surf)   # if "box" and single object => the box is not split, surface s1 out of the box.
         # if sgn == 1 :                 # The sign is the side of surface s1 where the box is located 
         #    # return ((1,0),(0,0)),None  # return the diagonal element of the Constraint Table for s1
         #    return ((1,0),(0,0)),None  # return the diagonal element of the Constraint Table for s1
         # else: 
         #    return ((0,0),(0,1)),None

       else:
          posSol = []
          negSol = []
          for s in comsolid.Solids:
             sgn = checkSign(s,surf)
             if sgn == 1:
                 posSol.append(s)
             else:
                 negSol.append(s)
          return 0, (posSol,negSol) # return the diagonal element of the Constraint Table for s1, and solids to be split by s2       
          #return ((1,0),(0,1)), (posSol,negSol) # return the diagonal element of the Constraint Table for s1, and solids to be split by s2       

    else:
       # "not box" => return the position of the +/- region of s1 (the solid) with respect s2
       if surf.shape :
           dist = solid.distToShape(surf.shape)[0]
       else:
           dist = 1.
       if dist > 1e-6 : # face doesn't intersect solid
         sgn = checkSign(solid,surf)
         if sgn == 1:
            return  (1,0)   # values of s2 and -s2   "0" means this region doesn't exist
         else:
            return  (0,1)  
       else:
          return (1,1)   # values of s2 and -s2
          

               
               
# find one point inside a solid (region)
def point_inside(solid):

      point = solid.CenterOfMass
      if solid.isInside(point,0.0,False) : return point

      cut_line = 32
      cut_box  = 4

      v1=solid.Vertexes[0].Point
      for vi in range(len(solid.Vertexes)-1,0,-1):
        v2=solid.Vertexes[vi].Point
        dv= (v2-v1)*0.5
      
        n=1
        while True:
           for i in range(n): 
              point = v1 + dv *(1 + 0.5*i)
              if solid.isInside(point,0.0,False) : return point
           n=n*2
           dv=dv*0.5
           if (n > cut_line ) :
             break  

      BBox = solid.optimalBoundingBox(False)
      box = [BBox.XMin,BBox.XMax,
             BBox.YMin,BBox.YMax,
             BBox.ZMin,BBox.ZMax ]
      
      boxes,centers = CutBox(box)
      n = 0

      while True :
         for p in centers :
            pp = FreeCAD.Vector(p[0],p[1],p[2])
            if solid.isInside(pp,0.0,False) : return pp

         subbox = []
         centers = []
         for b in boxes :
            btab,ctab = CutBox(b)   
            subbox.extend(btab)
            centers.extend(ctab)
         boxes = subbox
         n = n + 1

         if (n == cut_box ) :
           print('Solid not found in bounding Box (Volume : {})'.format(solid.Volume))
           return None

# divide a box into 8 smaller boxes
def CutBox(Box):
     xmid = (Box[1]+Box[0])*0.5
     ymid = (Box[3]+Box[2])*0.5
     zmid = (Box[5]+Box[4])*0.5

   
     b1 = (Box[0],xmid,Box[2],ymid,Box[4],zmid)
     p1 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b2 = (xmid,Box[1],Box[2],ymid,Box[4],zmid)
     p2 = (0.5*(xmid+Box[1]), 0.5*(Box[2]+ymid), 0.5*(Box[4]+zmid))
   
     b3 = (Box[0],xmid,ymid,Box[3],Box[4],zmid)
     p3 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))

     b4 = (xmid,Box[1],ymid,Box[3],Box[4],zmid)
     p4 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(Box[4]+zmid))
      
     b5 = (Box[0],xmid,Box[2],ymid,zmid,Box[5])
     p5 = (0.5*(Box[0]+xmid),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b6 = (xmid,Box[1],Box[2],ymid,zmid,Box[5])
     p6 = (0.5*(xmid+Box[1]),0.5*(Box[2]+ymid),0.5*(zmid+Box[5]))
   
     b7 = (Box[0],xmid,ymid,Box[3],zmid,Box[5])
     p7 = (0.5*(Box[0]+xmid),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))
     
     b8 = (xmid,Box[1],ymid,Box[3],zmid,Box[5])
     p8 = (0.5*(xmid+Box[1]),0.5*(ymid+Box[3]),0.5*(zmid+Box[5]))

     return (b1,b2,b3,b4,b5,b6,b7,b8),(p1,p2,p3,p4,p5,p6,p7,p8)        


def checkSign(solid,surf):

   point = point_inside(solid)

   if surf.Type == 'Plane':
       r = point - surf.Surf.Position 
       if surf.Surf.Axis.dot(r) > 0 :
           return 1
       else:
           return -1

   elif surf.Type == 'Cylinder':
       r = point - surf.Surf.Center
       L2 = r.Length*r.Length
       z  = surf.Surf.Axis.dot(r)
       z2 = z*z
       R2 = surf.Surf.Radius*surf.Surf.Radius
       if L2-z2 > R2 :
           return 1
       else:
           return -1

   elif surf.Type == 'Sphere':
       r = point - surf.Surf.Center
       if r.Length > surf.Surf.Radius :
           return 1
       else:
           return -1  

   elif surf.Type == 'Cone':
       r = point - surf.Surf.Apex
       r.normalize()
       z  = surf.Surf.Axis.dot(r)
       alpha = math.acos(z)

       if alpha > surf.Surf.SemiAngle :
           return 1
       else:
           return -1      

   elif surf.Type == 'Torus':
       r   = point - surf.Surf.Center
       h   = r.dot(surf.Surf.Axis)
       rho = r - h * surf.Surf.Axis 

       rp  = math.sqrt((rho.Length - surf.Surf.MajorRadius)**2 + h**2)
       if rp > surf.Surf.MinorRadius:
          return 1
       else: 
          return -1


src/GEOUNED/Utils/BooleanSolids.py
GEOUNED-code/GEOUNED
#
# Set of useful functions used in different parts of the code
#

import GEOUNED.Utils.BasicFunctions_part2 as BF

from GEOUNED.Utils.BasicFunctions_part1 import isParallel,\
     PlaneParams, CylinderParams, ConeParams, SphereParams, TorusParams 
from GEOUNED.Utils.Options.Classes import Tolerances as tol
import copy
import FreeCAD, Part
import math
import numpy as np

def makePlane(Plane,Boxin):

     normal = Plane.Surf.Axis
     p0     = Plane.Surf.Position.dot(normal)

     Box=FreeCAD.BoundBox(Boxin)
     Box.enlarge(10)

     pointEdge=[]
     for i in range(12):
       edge = Box.getEdge(i)
       p1 = normal.dot(edge[0])
       p2 = normal.dot(edge[1])
       d0 = p0 - p1
       d1 = p2 - p1
       if d1 != 0 :
         a = d0/d1
         if (a >= 0 and a <= 1):
            pointEdge.append(edge[0]+a*(edge[1]-edge[0]))
  
     if len(pointEdge) == 0 : return
     s = FreeCAD.Vector((0,0,0))
     for v in pointEdge :
       s = s + v
     s = s /  len(pointEdge) 
   
     vtxvec = []
     for v in pointEdge :
        vtxvec.append(v-s)

     X0 = vtxvec[0]
     Y0 = normal.cross(X0)

     orden=[]
     for i,v in enumerate(vtxvec):
       phi = np.arctan2(v.dot(Y0),v.dot(X0))
       orden.append((phi,i))
     orden.sort()
    
     return Part.Face( Part.makePolygon([ pointEdge[p[1]] for p in orden ], True))


class GEOUNED_Solid:
    
    def __init__(self,id,comsolid=None):
       refine = True
       if not comsolid : 
         self.Solids = None
         self.Volume = None
         self.BoundBox = None
       elif type(comsolid) is list :
          self.Solids = comsolid
          vol = 0
          self.BoundBox = FreeCAD.BoundBox()
          for s in comsolid:
             vol += s.Volume
             self.BoundBox.add(s.BoundBox)
          self.Volume = vol
       else:
         if refine :  
           try : 
              self.Solids = comsolid.removeSplitter().Solids
           except :
              self.Solids = comsolid.Solids

           for s in self.Solids :   
              if s.Volume < 0:
                 s.reverse()   
         else:
            self.Solids = comsolid.Solids 
         self.Volume = comsolid.Volume
         self.BoundBox = comsolid.BoundBox

       self.__id__ = id
       self.Definition = []
       self.Faces = []
       self.Comments = ''
       self.Density  = 0
       self.Dilution = 1
       self.Material = 0
       self.Surfaces = []
       self.Rho = None
       self.MatInfo = None
       self.CellType = 'solid'  #other types : 'void', 'enclosure', 'fill'
       self.Universe = 0
       self.Void = False
       self.IsEnclosure = False
       self.EnclosureID = None
       self.ParentEnclosureID = None
       self.SonEnclosures = []
       self.EnclosureList = None
       self.CADSolid = None
       self.UniverseBox = None
       self.NullCell = True

    def updateSolids(self,solidList):
        self.Solids = solidList
        vol = 0
        self.BoundBox = FreeCAD.BoundBox()
        for s in solidList:
          vol += s.Volume
          self.BoundBox.add(s.BoundBox)

    def setCADSolid(self):
        self.CADSolid = Part.makeCompound(self.Solids)

    def setUniverseBox(self,UniverseBox):
        self.UniverseBox = UniverseBox

    def setSonEnclosures(self,sonEnclosures):
        self.SonEnclosures = sonEnclosures      

    def setDefinition(self,definition,simplify=False):

       if definition is None:
          self.NullCell = True
          return

       self.NullCell = False
       self.Definition = definition 
       
       if not self.Void :
          if not self.Definition.elements:
             self.NullCell = True
             return

       self.Surfaces = self.Definition.getSurfacesNumbers()

        
    def setFaces(self,faces):
        self.Faces = faces

    def setComments(self,comments):
        self.Comments = comments

    def setMaterial(self,material,rho=None,info=None):

        self.Material = material
        self.Rho      = rho
        self.MatInfo  = info
          
        if rho is not None :
          self.Density = self.Dilution * rho 
        else:
          if material != 0 :
             self.Density = None
     
    def setDilution(self,dilution):
        self.Dilution = dilution
        if self.Rho is not None :
          self.Density = self.Rho*dilution

            
    def checkIntersection(self,solid,dtolerance=1.0e-6,vtolerance=1e-10):
        """ Check if solid intersect with current solid.
        return : -2 solid fully embedded in self.CADSolid ;
                 -1 self.CADSolid fully embedded in solid ;
                  0 self.CADSolid intersect solid ;
                  1 self.CADSolid and solid fully disjoint """

        dist = 1e12
        for sol1 in self.CADSolid.Solids:
            for sol2 in solid.Solids :
               try :  
                  distShape = sol1.distToShape(sol2)[0]
               except :
                  print('Failed solid1.distToshape(solid2), try with inverted solids')  
                  distShape = sol2.distToShape(sol1)[0]
                  print('inverted disToShape OK',distShape)  
               dist = min(dist,distShape)
               if dist == 0 : break

        if dist > dtolerance :
            return 1

        common = self.CADSolid.common(solid)
        if abs(common.Volume) < vtolerance :
            return 1
        if abs(self.CADSolid.Volume - common.Volume)/common.Volume < vtolerance :
           return -1
        elif abs(solid.Volume - common.Volume)/common.Volume < vtolerance :
            return -2
        else:
            return 0

               
class GEOUNED_Surface:
    
    def __init__(self,params,boundBox,Face=None) :
       
       self.Type    = params[0]
       self.Index   = 0
       self.__boundBox__ = boundBox
       if self.Type == 'Plane':
          self.Surf  = PlaneParams(params[1])  #plane point defined as the shortest distance to origin
       elif self.Type == 'Cylinder' :   
          self.Surf  = CylinderParams(params[1])
       elif  self.Type == 'Cone' :
          self.Surf  = ConeParams(params[1])
       elif self.Type == 'Sphere' :
          self.Surf  = SphereParams(params[1])
       elif self.Type == 'Torus' :
          self.Surf  = TorusParams(params[1])
       
       self.shape = Face

       if type(Face) is str :
          if Face == 'Build' : self.buildSurface()
                  
       if self.shape == 'Build' :
         print('stop',params,boundBox)
         exit()
       return       
           
       
    def buildSurface(self):
 
       if self.Type == 'Plane':

         normal = self.Surf.Axis        
         p0    = normal.dot(self.Surf.Position)
         Box = FreeCAD.BoundBox(self.__boundBox__)
         Box.enlarge(10)

         pointEdge=[]
         for i in range(12):
            edge = Box.getEdge(i)
            p1 = normal.dot(edge[0])
            p2 = normal.dot(edge[1])
            d0 = p0 - p1
            d1 = p2 - p1
            if d1 != 0 :
               a = d0/d1
               if (a >= 0 and a <= 1):
                  pointEdge.append(edge[0]+a*(edge[1]-edge[0]))
  
         if len(pointEdge) == 0 : 
             self.shape = None # Plane does not cross box
             return 

         s = FreeCAD.Vector((0,0,0))
         for v in pointEdge :
            s = s + v
         s = s /  len(pointEdge) 
   
         vtxvec = []
         for v in pointEdge :
            vtxvec.append(v-s)

         X0 = vtxvec[0]
         Y0 = normal.cross(X0)

         orden=[]
         for i,v in enumerate(vtxvec):
            phi = np.arctan2(v.dot(Y0),v.dot(X0))
            orden.append((phi,i))
         orden.sort()
    
         self.shape = Part.Face( Part.makePolygon([ pointEdge[p[1]] for p in orden ], True))
           
         return                 

       elif self.Type == 'Cylinder' :
         dir = self.Surf.Axis
         pnt = self.Surf.Center
         rad = self.Surf.Radius

         dmin = dir.dot((self.__boundBox__.getPoint(0)-pnt))
         dmax = dmin
         for i in range(1,8):
            d = dir.dot((self.__boundBox__.getPoint(i)-pnt))
            dmin = min(d,dmin)
            dmax = max(d,dmax)
        
         pnt = pnt + (dmin-5) * dir
         length = dmax-dmin+10
         self.shape = Part.makeCylinder(rad,length,pnt,dir,360.0)
         return 
   
       elif self.Type == 'Cone':
#         dir  = self.Surf.Axis
#         apex = self.Surf.Apex
#         tan = math.tan(self.Surf.SemiAngle)
#         rad = tan*diag # New radius far away to cover all geometry

         axis = self.Surf.Axis
         apex = self.Surf.Apex
         tan = math.tan(self.Surf.SemiAngle)

         dmax = axis.dot((self.__boundBox__.getPoint(0)-apex))
         for i in range(1,8):
            d = axis.dot((self.__boundBox__.getPoint(i)-apex))
            dmax = max(d,dmax)
        
         if dmax > 0 : 
            length =  dmax+10
            rad    =  tan*length
            self.shape = Part.makeCone(0.0,rad,length,apex,axis,360.0)
         else :
            self.shape = None
         return
         self.shape = cone

       elif self.Type == 'Sphere' :
         rad = self.Surf.Radius
         pnt = self.Surf.Center
         self.shape = Part.makeSphere(rad,pnt).Faces[0]
         return
        
       elif self.Type == 'Torus':
         axis   = self.Surf.Axis
         center = self.Surf.Center
         majorR = self.Surf.MajorRadius
         minorR = self.Surf.MinorRadius
         
         torus = Part.makeTorus(majorR,minorR,center,axis)
         self.shape = torus.Faces[0]
         return 
       else:
         print('Error: Type {} is not defined'.format(self.Type))
         return

       
class Surfaces_dict(dict):
   def __init__(self,surfaces=None,offset=0):
        self.IndexOffset = offset
        surfname = ['PX','PY','PZ','P','Cyl','Cone','Sph','Tor']    
        for name in surfname :
           self[name] = []

        if surfaces is not None:
          for key in surfaces.keys():
            self[key]=surfaces[key][:]
          self.surfaceNumber = surfaces.surfaceNumber
          self.__last_obj__ = (surfaces.__last_obj__[0],surfaces.__last_obj__[1])
        else:   
          self.surfaceNumber = 0
          self.__last_obj__ = ('',-1)
        return

   def __str__(self):
       for key in self.keys():
           print( key, self[key])
       return ''    

   def getSurface(self,index):
      
      lastKey = self.__last_obj__[0]
      lastInd = self.__last_obj__[1]
      if lastKey != '':
        if len(self[lastKey]) > 0 : 
          if self[lastKey][lastInd].Index == index :
            return self[lastKey][lastInd]
           
      for key in self.keys() :
         for i,s in enumerate(self[key]):
           if s.Index == index :
              self.__last_obj__ = (key,i)
              return s
      print ('Index {} not found in Surfaces'.format(index))
      return None

   def delSurface(self,index):         
      self.getSurface(index)
      del (self[self.__last_obj__[0]][self.__last_obj__[1]])
      return
       
   def extend(self,surface):
      for Pkey in ['PX','PY','PZ','P']:
        for s in surface[Pkey]:
          self.addPlane(s)
      for s in surface['Cyl']:
         self.addCylinder(s)
      for s in surface['Cone']:
         self.addCone(s)
      for s in surface['Sph']:
         self.addSphere(s)
      for s in surface['Tor']:
         self.addTorus(s)   

   def addPlane(self,plane,fuzzy=False):
        ex = FreeCAD.Vector(1,0,0)
        ey = FreeCAD.Vector(0,1,0)
        ez = FreeCAD.Vector(0,0,1)
        if isParallel(plane.Surf.Axis,ex,tol.pln_angle) :
           addPlane = True
           for i,p in enumerate(self['PX']):
              if BF.isSamePlane(plane.Surf,p.Surf,dtol=tol.pln_distance,atol=tol.pln_angle,relTol=tol.relativeTol,fuzzy=(fuzzy,p.Index)): 
                 addPlane = False
                 index = p.Index
                 self.__last_obj__ = ('PX',i)
                 break
           if addPlane : 
              self.surfaceNumber += 1 
              plane.Index = self.surfaceNumber + self.IndexOffset               
              self.__last_obj__ = ('PX',len(self['PX']))
              self['PX'].append(plane)

                      
        elif isParallel(plane.Surf.Axis,ey,tol.pln_angle) :
           addPlane = True
           for i,p in enumerate(self['PY']):
              if BF.isSamePlane(plane.Surf,p.Surf,dtol=tol.pln_distance,atol=tol.pln_angle,relTol=tol.relativeTol,fuzzy=(fuzzy,p.Index)) : 
                 addPlane = False
                 index = p.Index
                 self.__last_obj__ = ('PY',i)
                 break
           if addPlane :
              self.surfaceNumber += 1 
              plane.Index = self.surfaceNumber + self.IndexOffset               
              self.__last_obj__ = ('PY',len(self['PY']))
              self['PY'].append(plane)
               
        elif isParallel(plane.Surf.Axis,ez,tol.pln_angle) :
           addPlane = True
           for i,p in enumerate(self['PZ']):
              if BF.isSamePlane(plane.Surf,p.Surf,dtol=tol.pln_distance,atol=tol.pln_angle,relTol=tol.relativeTol,fuzzy=(fuzzy,p.Index)) : 
                 addPlane = False
                 index = p.Index
                 self.__last_obj__ = ('PZ',i)
                 break
           if addPlane :
              self.surfaceNumber += 1 
              plane.Index = self.surfaceNumber + self.IndexOffset              
              self.__last_obj__ = ('PZ',len(self['PZ']))
              self['PZ'].append(plane)
               
        else:
           addPlane = True
           for i,p in enumerate(self['P']):
              if BF.isSamePlane(plane.Surf,p.Surf,dtol=tol.pln_distance,atol=tol.pln_angle,relTol=tol.relativeTol,fuzzy=(fuzzy,p.Index)) : 
                 addPlane = False
                 index = p.Index
                 self.__last_obj__ = ('P',i)
                 break
           if addPlane :
              self.surfaceNumber += 1 
              plane.Index = self.surfaceNumber + self.IndexOffset             
              self.__last_obj__ = ('P',len(self['P']))
              self['P'].append(plane)

        if addPlane :
           return plane.Index, False
        else:
           return index, True       

   def addCylinder(self,cyl,fuzzy=False):
        addCyl = True
        for i,c in enumerate(self['Cyl']):
          if  BF.isSameCylinder(cyl.Surf,c.Surf,dtol=tol.cyl_distance,atol=tol.cyl_angle,relTol=tol.relativeTol,fuzzy=(fuzzy,c.Index)):
                addCyl = False
                index = c.Index
                self.__last_obj__ = ('Cyl',i)
                break
            
        if addCyl :
           self.surfaceNumber += 1 
           cyl.Index = self.surfaceNumber + self.IndexOffset               
           self.__last_obj__ = ('Cyl',len(self['Cyl']))
           self['Cyl'].append(cyl)
           return cyl.Index, False
        else: 
           return index, True
         
   def addCone(self,cone):
        addCone = True
        for i,c in enumerate(self['Cone']):
          if  BF.isSameCone(cone.Surf,c.Surf,dtol=tol.kne_distance,atol=tol.kne_angle,relTol=tol.relativeTol):
                addCone = False
                index = c.Index
                self.__last_obj__ = ('Cone',i)
                break
        if addCone :
           self.surfaceNumber += 1 
           cone.Index = self.surfaceNumber + self.IndexOffset               
           self.__last_obj__ = ('Cone',len(self['Cone']))
           self['Cone'].append(cone)
           return cone.Index, False
        else:
           return index, True
    
   def addSphere(self,sph):
        addSphere = True
        for i,s in enumerate(self['Sph']):
          if  BF.isSameSphere(sph.Surf,s.Surf,tol.sph_distance,relTol=tol.relativeTol):
                addSphere = False
                index = s.Index
                self.__last_obj__ = ('Sph',i)
                break
        if addSphere :
           self.surfaceNumber += 1 
           sph.Index = self.surfaceNumber + self.IndexOffset               
           self.__last_obj__ = ('Sph',len(self['Sph']))
           self['Sph'].append(sph)
           return sph.Index, False
        else:
           return index, True


   def addTorus(self,tor):
        addTorus = True
        for i,s in enumerate(self['Tor']):
          if  BF.isSameTorus(tor.Surf,s.Surf,dtol=tol.tor_distance,atol=tol.tor_angle,relTol=tol.relativeTol):
                addTorus = False
                index = s.Index
                self.__last_obj__ = ('Tor',i)
                break
        if addTorus :
           self.surfaceNumber += 1 
           tor.Index = self.surfaceNumber + self.IndexOffset               
           self.__last_obj__ = ('Tor',len(self['Tor']))
           self['Tor'].append(tor)
           return tor.Index, False
        else:
           return index, True

    

src/GEOUNED/Utils/Functions.py
GEOUNED-code/GEOUNED
#
#  definition of GEOUNED objects to release memory
#
import FreeCAD, Part
import math
from GEOUNED.Utils.BasicFunctions_part1 import isSameValue
from GEOUNED.Utils.BasicFunctions_part2 import isSameTorus
from GEOUNED.Utils.Options.Classes      import Tolerances as tol

# SURFACES
class Surfaces_GU(object):

    def __init__(self,face):
    
        self.face           = face
        self.Surface        = self.face.Surface
        self.type           = str(self.Surface)

    def __str__(self): 
        """ str(Surface) is done for the classification of the surface.
            Surface_GU saves this information in self.type"""
        return self.type
        
class Plane_GU(Surfaces_GU):

    def __init__(self,face):
    
        Surfaces_GU.__init__(self,face)
        self.Axis = face.Surface.Axis
        self.Position = face.Surface.Position
        self.dim1     = face.ParameterRange[1]-face.ParameterRange[0]
        self.dim2     = face.ParameterRange[3]-face.ParameterRange[2]

class Cylinder_GU(Surfaces_GU):

    def __init__(self,face):
    
        Surfaces_GU.__init__(self,face)
        self.Axis = face.Surface.Axis
        self.Radius = face.Surface.Radius
        self.Center = face.Surface.Center
        self.dimL   = face.ParameterRange[3]-face.ParameterRange[2]
        
class Cone_GU(Surfaces_GU):

    def __init__(self,face):
    
        Surfaces_GU.__init__(self,face)
        self.Axis = face.Surface.Axis
        self.Apex = face.Surface.Apex
        self.SemiAngle = face.Surface.SemiAngle
        self.dimL = face.ParameterRange[3]-face.ParameterRange[2]
        self.dimR = face.Surface.Radius
        self.Radius = face.Surface.Radius
        
class Sphere_GU(Surfaces_GU):

    def __init__(self,face):
    
        Surfaces_GU.__init__(self,face)
        self.type=self.type[0:6]
        self.Center = face.Surface.Center
        self.Radius = face.Surface.Radius

class Torus_GU(Surfaces_GU):

    def __init__(self,face):
    
        Surfaces_GU.__init__(self,face)
        self.Center      = face.Surface.Center
        self.Axis        = face.Surface.Axis
        self.MajorRadius = face.Surface.MajorRadius
        self.MinorRadius = face.Surface.MinorRadius 
        
class solid_GU():

    def __init__(self,solid):
        self.solid=solid
        faces = DefineListFace_GU(solid.Faces)
        self.Faces=faces
        self.Solids=solid.Solids
        self.BoundBox=solid.BoundBox
        self.Edges=solid.Edges
        self.TorusVParams = {}
        self.TorusUParams = {}

        toroidIndex=[]
        for i,face in enumerate(self.Faces):
            if str(face.Surface) != '<Toroid object>' : continue
            toroidIndex.append(i)

        if len(toroidIndex) != 0:
            tFaces = self.__sameTorusSurf__(toroidIndex)
            for i,tSet in enumerate(tFaces) :
               URange = self.__mergePeriodicUV__('U',tSet)
               VRange = self.__mergePeriodicUV__('V',tSet)
               for t in tSet:
                  self.TorusVParams[t] = (i,VRange)
                  self.TorusUParams[t] = (i,URange)

    def __sameTorusSurf__(self,torusList):

      # group identical faces
        sameTorusFace = []
        temp = torusList[:]
        while len(temp) > 0 :
           i = temp[0]
           current=[i]
           for j in temp[1:]:
              if isSameTorus(self.Faces[i].Surface,self.Faces[j].Surface,dtol=tol.tor_distance,atol=tol.tor_angle,relTol=tol.relativeTol):
                 current.append(j)
           for c in current:
              temp.remove(c)
           sameTorusFace.append(current)

        return self.__separateSurfaces__(sameTorusFace)
                  

    def __separateSurfaces__(self,faceList):
        # group all faces forming a continuous surface
        sameSurfaces = []
        for tset in faceList:
           temp = tset[:]
           while len(temp) > 0 :
             i = 0  
             current = [temp[0]]
             removeList = [temp[0]] 
             while len(temp) > 0 and i < len(current) :
                for tindex in temp:
                    if self.Faces[current[i]].distToShape(self.Faces[tindex])[0] < tol.distance :
                        if tindex not in current :
                            current.append(tindex)
                            removeList.append(tindex)
                i += 1
                for c in removeList :
                    temp.remove(c)
                removeList=[]    
             
             sameSurfaces.append(current) 
        return sameSurfaces

    def __mergeNoPeriodicUV__(self,parameter,faceList):                 

       if parameter == 'U':
           i1=0
           i2=2
       elif parameter == 'V':
           i1 = 2
           i2 = 4
           
       Vmin,Vmax = self.Faces[faceList[0]].ParameterRange[i1:i2]
       for face in faceList[1:] :
           V0,V1 = self.Faces[face].ParameterRange[i1:i2]
           Vmin = min(Vmin,V0)
           Vmax = max(Vmax,V1)
       mergedParams = (False,(Vmin,Vmax))
               
       return mergedParams

    def __mergePeriodicUV__(self,parameter,faceList):                 
       twoPi = 2.0 * math.pi
       if parameter == 'U':
           i1=0
           i2=2
       elif parameter == 'V':
           i1 = 2
           i2 = 4
           
       params = []
       arcLength = 0.
       for face in faceList :
           V0,V1 = self.Faces[face].ParameterRange[i1:i2]
           arcLength += V1-V0 
           params.append((V0,V1))

       params.sort()
       V0 = params[0][0]
       V1 = params[-1][1]
       if isSameValue(arcLength, twoPi, tol.relativePrecision ):
           mergedParams = (True,(V0,V1))
       else:
           if isSameValue(V0, 0., tol.relativePrecision ) and isSameValue(V1, twoPi, tol.relativePrecision ) :
              for i in range(len(params)-1):
                 if not isSameValue(params[i][1], params[i+1][0], tol.relativePrecision ): break
              Vmin=params[i+1][0]-twoPi
              Vmax=params[i][1]
           else:   
              Vmin = params[0][0]
              Vmax = params[-1][1]
           mergedParams = (False,(Vmin,Vmax))
               
       return mergedParams

# FACES
class Faces_GU(object):
    def __init__(self,face):
        # GEOUNED based atributes
        self.__face__       = face
        self.Surface        = DefineSurface(face) # Define the appropiate GU Surface of the face

        # FreeCAD based Atributes
        self.Area           = face.Area
        self.CenterOfMass   = face.CenterOfMass
        self.ParameterRange = face.ParameterRange
        self.Orientation    = face.Orientation
        self.OuterWire      = face.OuterWire
        return

    def tessellate(self,val):
        self.__face__.tessellate(val) 
        return

    def getUVNodes(self):
        return self.__face__.getUVNodes()

    def isEqual(self,face):
        return self.__face__.isEqual(face.__face__)

    def valueAt(self,u,v):
        return self.__face__.valueAt(u,v)

    def distToShape(self,shape):
        if type(shape) is Part.Shape:
          return self.__face__.distToShape(shape)
        else:
          return self.__face__.distToShape(shape.__face__)
        

# Aux functions
def DefineListFace_GU(face_list):
    """ Return the list of the  corresponding Face_GU  object of a FaceList"""
    return tuple( Faces_GU(face) for face in face_list )

def DefineSurface(face):
    kind_surf=str(face.Surface)
    if kind_surf=='<Plane object>':
        Surf_GU=Plane_GU(face)
    elif kind_surf=='<Cylinder object>':
        Surf_GU=Cylinder_GU(face)
    elif kind_surf=='<Cone object>':
        Surf_GU=Cone_GU(face)
    elif kind_surf[0:6] == 'Sphere':
        Surf_GU=Sphere_GU(face)
    elif kind_surf == '<Toroid object>':
        Surf_GU=Torus_GU(face)
    return Surf_GU


def ListSurfaces(Surfaces):
    Faces=[]
    for elem in Surfaces:
        Faces.extend( DefineSurface(face) for face in elem )
    return Faces

src/GEOUNED/Utils/Geometry_GU.py
GEOUNED-code/GEOUNED
class Options:
    pass

class MCNP_numeric_format:
    pass

class Tolerances:
    pass



src/GEOUNED/Utils/Options/Classes.py
GEOUNED-code/GEOUNED
import os.path
from GEOUNED.Utils.Options.Classes import Options, Tolerances, MCNP_numeric_format

#default options values
forceCylinder  = True                           # Force using cylinders instead cones for auxillary surfaces of torus surface definition
newSplitPlane  = True                           # Use new module for planes splitting in decomposition module
delLastNumber  = False                          # Deleting the last word in the comment if it is a number
verbose        = False                          # Display information during conversion process
enlargeBox     = 2                              # Enlarge box Boundary when evaluating Ctable (dimension in mm)
nPlaneReverse  = 0                              # numbers of plane thresold whether cut of parallel planes are made fisrt with lowest or highest number
splitTolerance = 0                              # Fuzzy tolerance used in BOPTOOL Split function
quadricPY      = False                          # use quadric form of cones and cylinder not align with X,Y,Z axis when write openMC script file

tolValueDict = { 'relativeTol'        : False ,      
              'relativePrecision'  : 1.e-6 ,   # relative precision
              'value'              : 1.e-6 ,   # Tolerance in single value comparison
              'distance'           : 1.e-4 ,   # General Distance Tolerance
              'angle'              : 1.e-4 ,   # General Angle Tolerance
              'pln_distance'       : 1.e-4 ,   # distance between planes equal planes if distance between parallel planes < 1e-4 cm
              'pln_angle'          : 1.e-4 ,   # angle between axis. 1e-4 : planes separate each other 0.1mm each 1m 
              'cyl_distance'       : 1.e-4 ,   # distance between radius/center
              'cyl_angle'          : 1.e-4 ,   # angle between axis
              'sph_distance'       : 1.e-4 ,   # distance between radius/center
              'kne_distance'       : 1.e-4 ,   # distance between apex
              'kne_angle'          : 1.e-4 ,   # angle between semiangles/axis
              'tor_distance'       : 1.e-4 ,   # distance between Major/Minor radii/center
              'tor_angle'          : 1.e-4 }   # angle between axis


numValueDict = {  'P_abc'   : '14.7e'  ,     # Plane general a,b,c params
                  'P_d'     : '14.7e'  ,     # Plane general d     params
                  'P_xyz'   : '14.7e'  ,     # PX/PY/PZ            params
                  'S_r'     : '14.7e'  ,     # SO/SX/SY/SZ/S       radius
                  'S_xyz'   : '14.7e'  ,     # SO/SX/SY/SZ/S       center 
                  'C_r'     : '12f'    ,     # Cylinder            radius
                  'C_xyz'   : '12f'    ,     # Cylinder            center 
                  'K_xyz'   : '13.6e'  ,     # Cone                apex
                  'K_tan2'  : '12f'    ,     # Cone                tan^2 value
                  'T_r'     : '14.7e'  ,     # Torus               radii
                  'T_xyz'   : '14.7e'  ,     # Torus               center 
                  'GQ_1to6' : '18.15f' ,     # GQ 1 to 6 coefficients (order 2 x2,y2,z2,xy,...)
                  'GQ_7to9' : '18.15f' ,     # GQ 7 to 9 coefficients (order 1 x,y,z)
                  'GQ_10'   : '18.15f' }     # GQ 10 coefficient      (order 0)



# Set default  attributes to Options class
setattr(Options,'forceCylinder' ,forceCylinder)  
setattr(Options,'newSplitPlane' ,newSplitPlane)  
setattr(Options,'enlargeBox'    ,enlargeBox)  
setattr(Options,'delLastNumber' ,delLastNumber)  
setattr(Options,'verbose'       ,verbose)  
setattr(Options,'nPlaneReverse' ,nPlaneReverse)  
setattr(Options,'splitTolerance',splitTolerance)  
setattr(Options,'quadricPY'     ,quadricPY)  

# Set default  attributes to Tolerances class
for key in tolValueDict.keys():
    setattr(Tolerances,key,tolValueDict[key])


# Set default  attributes to MCNP number format class
for key in numValueDict.keys():
    setattr(MCNP_numeric_format,key,numValueDict[key])

src/GEOUNED/Utils/Options/__init__.py
GEOUNED-code/GEOUNED
#
# Script to obtain the Qform of a Cylinder
#
#  
#

import FreeCAD, Part
import math

def RotationMatrix(u,v):

    """ Definition of the rotation matrix for two vectors"""
    
    # defintion of the axis of rotation
    
    Axis=u.cross(v).normalize()
          
    u.normalize()
    v.normalize()
#    print(u, v)
#    print(u.Length, v.Length, Axis.Length)
    cose=u.dot(v)
    seno=u.cross(v).Length
        
    R = FreeCAD.Matrix()
    
    #cose=math.cos(angle)
    #seno=math.sin(angle)
    onecos=1.0-cose
    
    # 1st row
    R.A11=cose+Axis.x**2*onecos
    R.A12=Axis.x*Axis.y*onecos-Axis.z*seno
    R.A13=Axis.x*Axis.z*onecos+Axis.y*seno
    
    #2nd row
    R.A21=Axis.x*Axis.y*onecos+Axis.z*seno
    R.A22=cose+Axis.y**2*onecos
    R.A23=Axis.y*Axis.z*onecos-Axis.x*seno
    
    #3rd row
    R.A31=Axis.z*Axis.x*onecos-Axis.y*seno
    R.A32=Axis.z*Axis.y*onecos+Axis.x*seno
    R.A33=cose+Axis.z**2*onecos
    
    return R
    
def RotationMatrixAngleAxis(u,angle):

    """ Definition of the rotation matrix for an angle and the rotation axis"""
    
    # defintion of the exis of rotation
    
    Axis=u.normalize()
          
    cose=math.cos(angle)
    seno=math.sin(angle)
        
    R = FreeCAD.Matrix()
    
    onecos=1.0-cose
    
    # 1st row
    R.A11=cose+Axis.x**2*onecos
    R.A12=Axis.x*Axis.y*onecos-Axis.z*seno
    R.A13=Axis.x*Axis.z*onecos+Axis.y*seno
    
    #2nd row
    R.A21=Axis.x*Axis.y*onecos+Axis.z*seno
    R.A22=cose+Axis.y**2*onecos
    R.A23=Axis.y*Axis.z*onecos-Axis.x*seno
    
    #3rd row
    R.A31=Axis.z*Axis.x*onecos-Axis.y*seno
    R.A32=Axis.z*Axis.y*onecos+Axis.x*seno
    R.A33=cose+Axis.z**2*onecos
    
    return R
    
def QFormCyl(Axis,Pos,rad):

    R=RotationMatrix(FreeCAD.Vector(1,0,0),Axis)
    R.transpose()
    Pos2=R.multiply(Pos).negative()
    
    A=R.A21**2+R.A31**2
    B=R.A22**2+R.A32**2
    C=R.A23**2+R.A33**2
    
    D=2.0*(R.A21*R.A22+R.A31*R.A32)
    E=2.0*(R.A22*R.A23+R.A32*R.A33)
    F=2.0*(R.A23*R.A21+R.A33*R.A31)
    
    G=2.0*(Pos2.y*R.A21+Pos2.z*R.A31)
    H=2.0*(Pos2.y*R.A22+Pos2.z*R.A32)
    J=2.0*(Pos2.y*R.A23+Pos2.z*R.A33)
    
    K=Pos2.y**2+Pos2.z**2-rad**2
    
    return (A,B,C,D,E,F,G,H,J,K)
    
def QFormCone(Axis,Pos,tan):

    R=RotationMatrix(FreeCAD.Vector(1,0,0),Axis)
    R.transpose()
    Pos2=R.multiply(Pos).negative()
    
    A=R.A21**2+R.A31**2-(tan*R.A11)**2
    B=R.A22**2+R.A32**2-(tan*R.A12)**2
    C=R.A23**2+R.A33**2-(tan*R.A13)**2
    
    D=2.0*(R.A21*R.A22+R.A31*R.A32-tan**2*R.A11*R.A12)
    E=2.0*(R.A22*R.A23+R.A32*R.A33-tan**2*R.A12*R.A13)
    F=2.0*(R.A23*R.A21+R.A33*R.A31-tan**2*R.A13*R.A11)
    
    G=2.0*(Pos2.y*R.A21+Pos2.z*R.A31-tan**2*Pos2.x*R.A11)
    H=2.0*(Pos2.y*R.A22+Pos2.z*R.A32-tan**2*Pos2.x*R.A12)
    J=2.0*(Pos2.y*R.A23+Pos2.z*R.A33-tan**2*Pos2.x*R.A13)
    
    K=Pos2.y**2+Pos2.z**2-(tan*Pos2.x)**2
    
    return (A,B,C,D,E,F,G,H,J,K)
    

    
    
    
    
    

    

src/GEOUNED/Utils/Qform.py
GEOUNED-code/GEOUNED
import re
mostinner=re.compile(r"\([^\(^\)]*\)")                                      # identify most inner parentheses
number   =re.compile(r"(?P<value>[-+]?\d+)")
mix      =re.compile(r"(?P<value>([-+]?\d+|\[0+\]))")
TFX      =re.compile(r"(?P<value>[FTXo]+)")
PValue   =re.compile(r"P\d+")
NValue   =re.compile(r"N\d+")
conversion = {'T':True,'F':False,'X':None}

class BoolSequence:
    def __init__(self,definition=None,operator=None) :
        if definition :
            self.elements = []
            self.setDef(definition)
        else:    
            self.elements = []
            self.operator = operator
            self.level    = 0

    def __str__(self):
       out='{}['.format(self.operator)
       if type(self.elements) is bool : return ' True ' if self.elements else ' False ' 
       for e in self.elements:
          if type(e) is int or type(e) is bool or type(e) is str : 
             out += ' {} '.format(e)
          else :
             out += e.__str__()

       out += '] '
       return out

    def append(self,*seq):
       for s in seq:
         if type(s) is int :
             level = -1
             if s in self.elements :
                 continue
             elif -s in self.elements:
                self.level = 0
                if self.operator == 'AND' :
                   self.elements = False 
                else:
                   self.elements = True
                return
         else:
             level = s.level 
             if type(s.elements) is bool:
               if self.operator == 'AND' and s.elements == False  or\
                  self.operator == 'OR'  and s.elements == True   : 
                  self.level = 0
                  self.elements = s.elements 
                  return
               else:
                  continue

         self.elements.append(s)
         self.level = max(self.level,level+1)

    def assign(self,Seq):
        self.operator = Seq.operator
        self.elements = Seq.elements
        self.level    = Seq.level 

    def copy(self):
        cp = BoolSequence()
        cp.operator= self.operator
        cp.level = self.level
        if type(self.elements) is bool:
           cp.elements = self.elements
        else:
           for e in self.elements:
              if type(e) is int :
                 cp.elements.append(e)
              else:
                 cp.elements.append(e.copy())
        return cp        
        
    def getComplementary(self):

       c = BoolSequence(operator=self.compOperator())
       c.level = self.level

       if self.level == 0:
          for e in self.elements:
             c.elements.append(-e) 
          return c
       else:
         self.groupSingle()
         for e in self.elements:
            c.elements.append( e.getComplementary() )
         return c        

                
    def compOperator(self):
       if self.operator == 'AND': 
          return 'OR'
       else:
          return 'AND'


    def simplify(self,CT,loop=0):
       surfNames = self.getSurfacesNumbers()
       if not surfNames : return
       #print(CT)
       #print('start',self)
       newNames = surfNames
       simplified = False
       for valname in surfNames:
          #print('factorise',valname)
          if valname in newNames: 
             chg = self.factorize(valname,CT)
             simplified = simplified or chg
             newNames = self.getSurfacesNumbers()
       self.joinOperators()
       
       if self.level == 0 or (self.elements) is bool : return

       if loop == 0:
          ANDSeq = BoolSequence(operator=self.operator)
          ORSeq  = BoolSequence(operator=self.operator)
          for e in self.elements:
             if e.operator == 'AND':
                ANDSeq.append(e) 
             else:
                ORSeq.append(e) 

          ANDSeq.simplify(CT,loop=1)
          ORSeq.simplify(CT,loop=1)
          newSeq = BoolSequence(operator=self.operator)
          if ANDSeq.elements: newSeq.append(ANDSeq) 
          if ORSeq.elements : newSeq.append(ORSeq) 
          newSeq.joinOperators()
          self.assign(newSeq)
       else:
          for e in reversed(self.elements):
              e.simplify(CT,loop=0)
              if type(e.elements) is bool:
                 if self.operator == 'AND' and e.elements == False or \
                    self.operator == 'OR' and e.elements == True  : 
                    self.elements = e.elements
                    self.level = 0
                    break
                 else:
                    self.elements.remove(e)

          if self.elements == []:
             self.level = 0
             if self.operator == 'AND' : 
                self.elements = True 
             else:
                self.elements = False

     # check if level 0 sequence have oposite value a & -a = 0  , a|-a = 1
     # return the value of the sequence None(unknown), True, False
    def check(self):
       if self.level == 0:
          if type(self.elements) is bool : return self.elements

          signedSurf = set(self.elements)
          surfname  = self.getSurfacesNumbers()
          if len(signedSurf) == len(surfname) :  return None  # means same surface has not positive and negative value
          elif self.operator == 'AND' :          return False
          else:                                  return True
       else:
           if type(self.elements) is bool : return self.elements

           self.groupSingle()
           noneVal = False
           for e in self.elements:
              res = e.check()
              if res is None : noneVal = True
              elif self.operator == 'AND' and res is False : return False
              elif self.operator == 'OR'  and res is True  : return True

           if    noneVal :                return None
           elif  self.operator == 'AND' : return True
           else:                          return False


    def substitute(self,var,val):
       if   type(self.elements) is bool: return
       name = abs(var)
       ic = len(self.elements)
       for e in reversed(self.elements):
          ic -= 1
          if type(e) is int:
             if abs(e) == name :
                if type(val) is int : 
                   if name == e : self.elements[ic] = val
                   else         : self.elements[ic] = -val

                else :
                   if name == e : boolValue = val
                   else         : boolValue = not val

                   if self.operator == 'AND' and not boolValue :
                        self.elements = False
                        self.level    = 0
                        return
                   elif self.operator == 'OR' and boolValue :
                        self.elements = True
                        self.level    = 0
                        return
                   else:
                        self.elements.remove(e)
          else:
             e.substitute(var,val) 
 
       self.clean()
       self.levelUpdate()



    # remove sequence whom elements are boolean values instead of list
    def clean(self):   
        if type(self.elements) is bool : return self.elements
        for e in reversed(self.elements) :
           if type(e) is int : continue
           eVal = e.clean()
           if type(eVal) is bool:
              if eVal and self.operator == 'OR'  : 
                 self.elements = True
                 self.level = 0
                 return True
              elif not eVal and self.operator == 'AND' : 
                 self.elements = False
                 self.level = 0
                 return False
              self.elements.remove(e)

        if self.elements == [] :
           if self.operator == 'OR' : self.elements = False
           else                     : self.elements = True
           self.level = 0
           return self.elements
        else:
           return None 


    # join redundant operators in sequence
    def joinOperators(self):
        if self.level == 0 : return
        if type(self.elements) is bool: return
        self.groupSingle()
        ANDop = []
        ORop  = []
     
        for e in self.elements:
           if e.operator == 'AND': ANDop.append(e) 
           else                  : ORop.append(e)

        if   len(ANDop) > 1  and self.operator == 'AND':
           newSeq = BoolSequence(operator='AND')
           for s in ANDop :
             newSeq.elements.extend(s.elements)
             self.elements.remove(s)
           newSeq.levelUpdate()
           self.append(newSeq)

            
        elif len(ORop)  > 1  and self.operator == 'OR':
           newSeq = BoolSequence(operator='OR')
           for s in ORop :
             newSeq.elements.extend(s.elements)
             self.elements.remove(s)
           newSeq.levelUpdate()
           self.append(newSeq)

        if self.level > 0  and len(self.elements)==1 :
           self.operator = self.elements[0].operator
           self.elements = self.elements[0].elements
           self.level -= 1
           self.joinOperators()
       
        if self.level == 0 : return
        for e in self.elements:
           e.joinOperators()



    def factorize(self,valname,CT=None):

        if CT is None :
           trueSet  = {abs(valname) : True }
           falseSet = {abs(valname) : False }
        else:
           trueSet,falseSet =  CT.getConstraintSet(valname)

        if trueSet is None: 
             self.substitute(valname,False)
             return True

        funcVal = self.evaluate(trueSet,CT)
        if funcVal == False :   
            newSeq = BoolSequence(operator='AND')
            #self.substitute(valname,False)
            for name,value in falseSet.items():
               self.substitute(name,value)
            newSeq.append(-valname,self.copy())
            self.assign(newSeq)
            return True
        elif funcVal == True:
            newSeq = BoolSequence(operator='OR')
            #self.substitute(valname,False)
            for name,value in falseSet.items():
               self.substitute(name,value)
            newSeq.append(valname,self.copy())
            self.assign(newSeq)
            return True

        if falseSet is None: 
             self.substitute(valname,True)
             return True

        funcVal = self.evaluate(falseSet,CT)
        if funcVal == False :   
            newSeq = BoolSequence(operator='AND')
            #self.substitute(valname,True)
            for name,value in trueSet.items():
               self.substitute(name,value)
            newSeq.append(valname,self.copy())
            self.assign(newSeq)
            return True
        elif funcVal == True:
            newSeq = BoolSequence(operator='OR')
            #self.substitute(valname,True)
            for name,value in trueSet.items():
               self.substitute(name,value)
            newSeq.append(-valname,self.copy())
            self.assign(newSeq)
            return True

        return False
  

    def evaluate_newbad(self,valueSet,CT=None):

        if type(self.elements) is bool : return self.elements
        self.groupSingle()
        newSeq = self.copy()
        for name,value in valueSet.items():
            newSeq.substitute(name,value)
        
        if type(newSeq.elements) is bool :
            return newSeq.elements
        else :
            surfNames = newSeq.getSurfacesNumbers()        
            valname = surfNames[0]
            if CT is None :
                trueSet  = {abs(valname) : True }
                falseSet = {abs(valname) : False }
            else:
                trueSet,falseSet =  CT.getConstraintSet(valname)
                 
            if trueSet is None:
                trueVal = True
            else:       
                trueVal  = newSeq.evaluate(trueSet,CT)
                if trueVal is None : return None
               
            if falseSet is None:
                falseVal = False
            else:       
                falseVal  = newSeq.evaluate(falseSet,CT)
                if falseVal is None : return None
                    
            if trueVal != falseVal :
                return None
            else:
                return trueVal 


    def evaluate(self,valueSet,CT=None):
        if type(self.elements) is bool : return self.elements
        self.groupSingle()
        op = self.operator
        noneVal = False
        for e in self.elements:
            if type(e) is int:
               key = abs(e)
               if key in valueSet.keys():
                    val = valueSet[key]
               else:
                    val = None
                    
               if val is not None : 
                  if e < 0 : val = not val
               else:
                  noneVal = True
            else:
               val = e.evaluate(valueSet)

            if val is None : noneVal = True
            elif op == 'AND' and val is False : return False
            elif op == 'OR'  and val is True  : return True

        if noneVal : 
           return None      
        if op == 'AND' :
           return True
        else:     
           return False
        
    def setDef(self,expression):
       terms,operator = outterTerms(expression)
       self.operator = operator
       self.level = 0
       lev0Seq    = set()
       lev0SeqAbs = set() 
       for t in terms :
         if isInteger(t) :
            val = int(t.strip('(').strip(')'))
            lev0Seq.add( val )
            lev0SeqAbs.add(abs(val))
            #self.elements.append(int(t.strip('(').strip(')')))
         else:
            x = BoolSequence(t) 
            self.level = max(x.level+1,self.level)
            self.elements.append(x)
       
       # check if in integer sequence there is surface sequence s -s 
       if len(lev0Seq) != len(lev0SeqAbs) :
          if self.operator == 'AND' :
             self.elements = False
          else:
             self.elements = True
       else:
           self.elements.extend(lev0Seq)
          
       self.groupSingle()     

    def groupSingle(self):
       if self.level == 0 : return
       if type(self.elements) is bool : return
       group = []
       for e in reversed(self.elements):
         if type(e) is int : 
            group.append(e)
            self.elements.remove(e)
         elif e.level==0 and len(e.elements) == 1 :
            group.append(e.elements[0])
            self.elements.remove(e)
            

       if not group : return
       seq = BoolSequence()
       seq.elements.extend(group)
       seq.operator = self.operator
       seq.level = 0
       self.elements.insert(0,seq)


    def getSurfacesNumbers(self):
        if type(self.elements) is bool : return tuple()
        surf = set()
        for e in self.elements:
           if type(e) is int :
                surf.add(abs(e))
           else:
                surf.update(e.getSurfacesNumbers())
        return tuple(surf)             

    def levelUpdate(self):
       if type(self.elements) is bool :
          self.level = 0
          return

       newlev = 0
       for e in self.elements:
          if type(e) is int :
             lev=0
          else:
             lev=e.level+1
          newlev = max(lev,newlev)
       self.level = newlev


def outterTerms(expression,value='number'):
      if value == 'number' :
          #reValue = number
          reValue = mix
          nullVal = '0'
      else:
          reValue = TFX
          nullVal = 'o'
          
      expr = expression
      

      # Loop until no redundant parentheses are found
      cont = True
      
      while cont:
        # Loop over most inner parentheses
        pos = 0
        cont = False
        while True :
          m = mostinner.search(expr,pos)
          if not m : break
          cont = True
          if redundant(m,expr):
             # remove redundant parentheses
             expr = expr[:m.start()]+ ' ' + expr[m.start()+1:m.end()-1]+ ' ' + expr[m.end():]
          else:
             # replace no redundant parentheses by 0 and : by ;
             zeros = '[' + nullVal* (m.end()-m.start()-2) + ']' 
             expr = expr[:m.start()] + zeros + expr[m.end():]

          pos = m.end()

      if ':' in expr :
          terms = []
          pos = 0
          while True :
              newpos = expr.find(':',pos)
              if newpos == -1 :
                  terms.append(expression[pos:].strip())
                  break
              terms.append(expression[pos:newpos].strip())
              pos = newpos + 1                       
          return (terms,'OR')
      else:
          terms = []
          pos = 0
          while True:
              m = reValue.search(expr,pos)
              if not m : break
              terms.append(expression[m.start():m.end()])
              pos = m.end()                          
          return (terms,'AND')
        

def redundant(m,geom):
   """ check if the inner parentheses are redundant """
   term = m.group()

   # Find first valid character at the left of the  parenthese
   leftOK= True
   left = m.start()-1
   while left > -1:
       if geom[left] in ('\n','C','$',' '):
          left -= 1
       else:
          if geom[left] not in ('(',':') : leftOK  = False
          break

  # check if no ':' (or) are inside the parenthese
  # if not, parentheses are redundants
   if (term.find(':') == -1) : return True

  # Find first valid character at the right of the  parenthese
   rightOK= True
   right = m.end()
   while right < len(geom)  :
       if geom[right] in ('\n','C','$',' '):
          right += 1
       else:
          if geom[right] not in (')',':') : rightOK  = False
          break

  # if parentheses are like:
  # {( or : } ( ....... ) {) or :}
  # parentheses are redundants

   if leftOK and rightOK :
       return True
   else:
       return False

def isInteger(x):
    try :
      int(x.strip('(').strip(')'))
      return True
    except:
      return False  

src/GEOUNED/Utils/booleanFunction.py
GEOUNED-code/GEOUNED
from GEOUNED.Void.VoidBoxClass import VoidBox
from GEOUNED.Utils.BasicFunctions_part1 import isOposite
from GEOUNED.Utils.Functions import GEOUNED_Solid, GEOUNED_Surface
from GEOUNED.Utils.booleanFunction import BoolSequence
from GEOUNED.Utils.Options.Classes import Options as opt
import GEOUNED.LoadFile.LoadFunctions as LF
import GEOUNED.Void.voidFunctions as VF
import FreeCAD, Part

def voidGeneration(MetaList,EnclosureList,Surfaces,UniverseBox,setting,init):
    voidList = []
     
    if EnclosureList:
       NestedEnclosure = LF.setEnclosureLevels(EnclosureList)
       VF.assignEnclosure(MetaList,NestedEnclosure)
       
       # add to Metalist Level 1 enclosures, remove from list material cells totally embedded in Level 1 enclosures
       newMetaList = VF.selectSolids(MetaList,NestedEnclosure[0],UniverseBox) 
    else:   
       newMetaList = MetaList[:]
       NestedEnclosure = []

    Box = Part.makeBox(UniverseBox.XLength, UniverseBox.YLength, UniverseBox.ZLength,
                      FreeCAD.Vector(UniverseBox.XMin, UniverseBox.YMin, UniverseBox.ZMin),
                      FreeCAD.Vector(0,0,1))
    
    EnclosureBox = GEOUNED_Solid(None,Box)
    if setting['voidMat']:
        voidMat = setting['voidMat']
        EnclosureBox.setMaterial(voidMat[0],voidMat[1],voidMat[2])
        

    # get voids in 0 Level Enclosure (original Universe)
    # if exist Level 1 enclosures are considered has material cells
    print('Build Void highest enclosure')
       
    voids = GetVoidDef(newMetaList,Surfaces,EnclosureBox,setting,Lev0=True)
    voidList.append(voids)

    # Perform enclosure void
    # Loop until the lowest enclosure level
    
    for i,Level in enumerate(NestedEnclosure) :
        
        print('Build Void highest enclosure')
        for j,encl in  enumerate(Level):
            if encl.CellType == 'envelope': continue
            newMetaList = VF.selectSolids(MetaList,encl.SonEnclosures,encl)
            print('Build Void enclosure {} in enclosure level {}'.format(j,i+1)) 
            # select solids overlapping current enclosure "encl", and lower level enclosures
            voids = GetVoidDef(newMetaList,Surfaces,encl,setting)
            voidList.append(voids)
    
     
    voidList.append( setGraveyardCell(Surfaces,UniverseBox) )
 
    return VF.updateVoidList(init,voidList,NestedEnclosure,setting['sortEnclosure'])


def GetVoidDef(MetaList,Surfaces,Enclosure,setting,Lev0=False):

    maxsurf    = setting['maxSurf']
    maxbracket = setting['maxBracket']
    minSize    = setting['minVoidSize']
    
    if 'full' in setting['simplify'].lower() :
          simplifyVoid = 'full' 
    elif 'void' in setting['simplify'].lower() :
          simplifyVoid = 'diag' 
    else:
          simplifyVoid = 'no'

    if Lev0 :
        Universe = VoidBox(MetaList,Enclosure.BoundBox)
    else:  
        Universe = VoidBox(MetaList,Enclosure.CADSolid)

    Initial = [Universe]
    VoidDef  = []
    iloop = 0
    while iloop < 50:
      Temp  = []
      iloop += 1
      nvoid = len(Initial) 
      print('Loop, Box to Split :',iloop,nvoid)
      
      for iz,z in enumerate(Initial):
         nsurfaces,nbrackets = z.getNumbers()
         if opt.verbose : print('{} {}/{} {} {}'.format(iloop,iz,nvoid,nsurfaces,nbrackets))      

         if nsurfaces > maxsurf and nbrackets > maxbracket:
            newspace = z.Split(minSize)
         else:
            newspace = None

         if type(newspace) is tuple :   
            Temp.extend(newspace)
         else:
#           if len(z.Objects) >= 50 : z.refine()
            boxDim = (z.BoundBox.XMin*0.1,z.BoundBox.XMax*0.1,
                      z.BoundBox.YMin*0.1,z.BoundBox.YMax*0.1,
                      z.BoundBox.ZMin*0.1,z.BoundBox.ZMax*0.1)

            CellIn = [O.__id__ for O in z.Objects]
            print('build complementary {} {}'.format(iloop,iz))
            cell = z.getVoidComplementary(Surfaces,simplify=simplifyVoid) 
            if cell is not None :					
               VoidCell = (cell,(boxDim,CellIn))
               VoidDef.append(VoidCell)  

      Initial = Temp
      if len(Temp) == 0 :break

    voidList = []
    for i,vcell in enumerate(VoidDef):
      mVoid      = GEOUNED_Solid(i)
      mVoid.Void = True
      mVoid.CellType = 'void'
      mVoid.setDefinition(vcell[0],simplify=True) 
      mVoid.setComments(voidCommentLine(vcell[1]))
      mVoid.setMaterial(Enclosure.Material,Enclosure.Rho,Enclosure.MatInfo)
      mVoid.setDilution(Enclosure.Dilution)
      voidList.append(mVoid)
      
    return voidList

def setGraveyardCell(Surfaces,UniverseBox):
    Universe = VoidBox([],UniverseBox)

    externalBox = getUniverseComplementary(Universe,Surfaces)
    center = UniverseBox.Center
    radius = 0.51*UniverseBox.DiagonalLength
    sphere = GEOUNED_Surface(('Sphere',(center,radius)),UniverseBox)
    id,exist = Surfaces.addSphere(sphere)

    sphdef = BoolSequence(str(-id))
    sphdef.operator = 'AND'
    sphdef.append(externalBox)

    notsph = BoolSequence(str(id))
      
    mVoidSphIn       = GEOUNED_Solid(0)
    mVoidSphIn.Void  = True
    mVoidSphIn.CellType  = 'void'
    mVoidSphIn.setDefinition(sphdef)
    mVoidSphIn.setMaterial(0,0,'Graveyard_in')

    mVoidSphOut      = GEOUNED_Solid(1)
    mVoidSphOut.Void = True
    mVoidSphOut.CellType  = 'void'
    mVoidSphOut.setDefinition(notsph)
    mVoidSphOut.setMaterial(0,0,'Graveyard')

    return (mVoidSphIn,mVoidSphOut)


def getUniverseComplementary(Universe,Surfaces):
    Def = BoolSequence(operator='OR')
    for p in Universe.getBoundPlanes():
       id,exist = Surfaces.addPlane(p)
       if not exist :
          Def.elements.append(-id)
       else:
          s = Surfaces.getSurface(id)
          if isOposite(p.Surf.Axis,s.Surf.Axis):
             Def.elements.append(id)
          else:    
             Def.elements.append(-id)
    return Def          
                 
def voidCommentLine(CellInfo):
    boxDef,cellIn = CellInfo
    cells = ', '.join(map(str,cellIn))
    box   = ', '.join(map(str,boxDef))
    line  = 'Automatic Generated Void Cell. Enclosure({})\n'.format(box)
    line += 'Enclosed cells : ({})\n'.format(cells)
    return line

src/GEOUNED/Void/Void.py
GEOUNED-code/GEOUNED
"""File with the VoidBox class"""
import FreeCAD
import Part
import GEOUNED.Decompose.Decom_one as Decom
import GEOUNED.Conversion.CellDefinition as Conv

from GEOUNED.Utils.Options.Classes import Options as opt
from GEOUNED.Utils.BasicFunctions_part1 import isOposite
from GEOUNED.Utils.Functions import GEOUNED_Solid, GEOUNED_Surface
from GEOUNED.Utils.booleanFunction import BoolSequence
from GEOUNED.Utils.BooleanSolids import buildCTableFromSolids, removeExtraSurfaces


class VoidBox (): 
  def __init__(self,MetaSolids,Enclosure):

    self.Objects =[]
    if 'BoundBox' in str(Enclosure) :
        self.BoundBox = Enclosure
        self.PieceEnclosure = None
    else:      
        self.BoundBox = Enclosure.BoundBox      
        self.PieceEnclosure = Enclosure

    for m in MetaSolids :
       if not m.BoundBox : continue
       if m.BoundBox.isValid() :
         if self.BoundBox.intersect(m.BoundBox) :
           Obj = self.__copyMeta__(m)
           self.__removeExtraComp__(Obj,self.BoundBox)
           self.Objects.append(Obj)    
    return
  
  def Split(self,minSize=200):
      
    dims = [self.BoundBox.XLength,self.BoundBox.YLength,self.BoundBox.ZLength]
    coord = ['X','Y','Z']
    for i in range(2,-1,-1):
      if 0.5*dims[i] < minSize :
        del(dims[i])
        del(coord[i])

    if len(dims) == 0 : return None   
      
    ip = dims.index(max(dims))
#    print ('dims : {} {}'.format(coord[ip],0.5*dims[ip]))
    if coord[ip] == 'X' : 
       pos   = self.BoundBox.XMin + 0.5*self.BoundBox.XLength
       X1Min = self.BoundBox.XMin
       X1Max = pos
       X2Min = pos
       X2Max = self.BoundBox.XMax
       Y1Min = self.BoundBox.YMin
       Y1Max = self.BoundBox.YMax
       Y2Min = Y1Min
       Y2Max = Y1Max
       Z1Min = self.BoundBox.ZMin
       Z1Max = self.BoundBox.ZMax
       Z2Min = Z1Min
       Z2Max = Z1Max
    elif coord[ip] == 'Y' :
       pos   = self.BoundBox.YMin + 0.5*self.BoundBox.YLength 
       X1Min = self.BoundBox.XMin
       X1Max = self.BoundBox.XMax
       X2Min = X1Min
       X2Max = X1Max
       Y1Min = self.BoundBox.YMin
       Y1Max = pos
       Y2Min = pos
       Y2Max = self.BoundBox.YMax
       Z1Min = self.BoundBox.ZMin
       Z1Max = self.BoundBox.ZMax
       Z2Min = Z1Min
       Z2Max = Z1Max  
    else  :
       pos   = self.BoundBox.ZMin + 0.5*self.BoundBox.ZLength 
       X1Min = self.BoundBox.XMin
       X1Max = self.BoundBox.XMax
       X2Min = X1Min
       X2Max = X1Max
       
       Y1Min = self.BoundBox.YMin
       Y1Max = self.BoundBox.YMax 
       Y2Min = Y1Min
       Y2Max = Y1Max
       
       Z1Min = self.BoundBox.ZMin
       Z1Max = pos
       Z2Min = pos
       Z2Max = self.BoundBox.ZMax

    VMin1 = FreeCAD.Vector(X1Min,Y1Min,Z1Min)
    VMax1 = FreeCAD.Vector(X1Max,Y1Max,Z1Max)
    VMin2 = FreeCAD.Vector(X2Min,Y2Min,Z2Min)
    VMax2 = FreeCAD.Vector(X2Max,Y2Max,Z2Max)
    box1 = FreeCAD.BoundBox(VMin1,VMax1)
    box2 = FreeCAD.BoundBox(VMin2,VMax2)
    
    if self.PieceEnclosure == None:
        Space1 = VoidBox(self.Objects,box1)
        Space2 = VoidBox(self.Objects,box2)
        VoidBoxTuple = (Space1,Space2)
    else:
        Space1 = self.PieceEnclosureSplit(box1)
        Space2 = self.PieceEnclosureSplit(box2)
        VoidBoxTuple = (Space1,Space2)
        if Space1 == None:
            VoidBoxTuple = (Space2,)
        if Space2 == None:
            VoidBoxTuple = (Space1,)
        if Space1 == None and Space2 == None:
            VoidBoxTuple = ()

    return VoidBoxTuple    


  def PieceEnclosureSplit(self,Box,Tolerance=1.0e-13):
    """This function creates a box-shaped solid with the new limits of given bounding box and
  it is intersected with the piece of nested enclosure to create the new void cell.
  If the limited region does not intersect with the piece, no void cell is created."""
    
    Cube = Part.makeBox(
        Box.XLength, Box.YLength, Box.ZLength,
        FreeCAD.Vector(Box.XMin, Box.YMin, Box.ZMin),
        FreeCAD.Vector(0,0,1))
    dist = Cube.distToShape(self.PieceEnclosure)[0]
    try:
        if abs(dist/Box.DiagonalLength) > Tolerance:
            return None
    except ZeroDivisionError:
        return None
    ShapeObject = Cube.common(self.PieceEnclosure)
    try:
        reldif = (Cube.Volume-ShapeObject.Volume)/Cube.Volume
    except ZeroDivisionError:
        return None
    if abs(reldif) <= Tolerance:
        return VoidBox(self.Objects,Box)
    elif ShapeObject.Solids :
        Solid = ShapeObject.Solids[0]
        return VoidBox(self.Objects,Solid)
    else:
        return None 

      
  def refine(self):
     Cube = Part.makeBox(self.BoundBox.XLength, self.BoundBox.YLength, self.BoundBox.ZLength,
                         FreeCAD.Vector(self.BoundBox.XMin, self.BoundBox.YMin, self.BoundBox.ZMin),
                         FreeCAD.Vector(0,0,1))
     refinedList = []
     newcom = []
       
     for m in self.Objects:
        self.__removeExtraComp__(m,Cube,mode='dist')
     return   

  def getVoidComplementary(self,Surfaces,simplify='No'):

    if self.PieceEnclosure is None:
        boxDef = BoolSequence(operator='AND')
        center = self.BoundBox.Center
        bBox = self.BoundBox
        for p in self.getBoundPlanes():
           id,exist = Surfaces.addPlane(p)
           if exist:
              s = Surfaces.getSurface(id)
              if isOposite(p.Surf.Axis,s.Surf.Axis):
                  id = -id
           if isOposite(p.Surf.Axis,p.Surf.Position-center):
               boxDef.elements.append(id)
           else:
               boxDef.elements.append(-id)
           enclosure = False
        d = opt.enlargeBox
        
    else:
        UniverseBox = self.PieceEnclosure.BoundBox
        TempPieceEnclosure = GEOUNED_Solid(None,self.PieceEnclosure)
        comsolid,err=Decom.SplitSolid(Part.makeCompound(TempPieceEnclosure.Solids),UniverseBox)
        Surfaces.extend(Decom.ExtractSurfaces(comsolid,'All',UniverseBox,MakeObj=True))
        TempPieceEnclosure.updateSolids(comsolid.Solids)
        Conv.cellDef(TempPieceEnclosure,Surfaces,UniverseBox)

        boxDef = TempPieceEnclosure.Definition
        bBox = self.PieceEnclosure.BoundBox
        enclosure = True
        d = max(opt.enlargeBox,2)

    Box = Part.makeBox(bBox.XLength+2*d, bBox.YLength+2*d, bBox.ZLength+2*d,
        FreeCAD.Vector(bBox.XMin-d, bBox.YMin-d, bBox.ZMin-d),
        FreeCAD.Vector(0,0,1))       

    voidSolidDef = BoolSequence(operator='OR')

    for m in self.Objects:
        voidSolidDef.append(m.Definition)

    voidSolidDef.joinOperators()

    if not voidSolidDef.elements : 
         return boxDef     #  Cell to get complementary are null => Void is only box definition
       
    # join all basic solids into one big meta Object
    # CAD solid representation is not needed because
    # here we are working with surfaces and void box


    complementary = BoolSequence(operator='AND')
    complementary.append(boxDef)

    if (simplify != 'no' ) :
       surfList = set(voidSolidDef.getSurfacesNumbers())

       if enclosure : 
           surfList.update(set(boxDef.getSurfacesNumbers()))
       else:
           for s in boxDef.elements:
              val = True if s >0 else False
              voidSolidDef.substitute(abs(s),val)
           res = voidSolidDef.clean()

       if enclosure or res is None :
          surfaceDict = {}
          for surf in Surfaces.values():
             for s in surf:
                if s.Index in surfList:
                   surfaceDict[s.Index] = s

          CTable = buildCTableFromSolids(Box,surfaceDict,option=simplify)
          voidSolidDef = removeExtraSurfaces(voidSolidDef,CTable) 
    
    if voidSolidDef.elements == True :
        return None
    elif voidSolidDef.elements == False or voidSolidDef.elements == []:
        return boxDef

    if voidSolidDef.level == 0:
       compSeq = voidSolidDef.getComplementary()
    else:
       compSeq = BoolSequence(operator='AND')
       for comp in voidSolidDef.elements :
          if simplify == 'no':
             chk =  comp.check()
             #solid in cover full Void cell volume  => Void cell doesn't exist
             if chk == True :
                 if opt.verbose: print('warning void Cell should not exist')
                 return None

             #solid cell is not in void cell Void cell volume  => doesn't contribute to void definition
             elif chk == False :
                 continue

          pmoc = comp.getComplementary()
          compSeq.append(pmoc)
   
    if simplify == 'full':
       if enclosure :
          complementary.append(compSeq)
          complementary.simplify(CTable)
       else :  
          compSeq.simplify(CTable)
          complementary.append(compSeq)
    else:
       compSeq.simplify(None)
       complementary.simplify(None)
       complementary.append(compSeq)
 

    res = complementary.clean()
    complementary.levelUpdate()
    
    if type(res) is bool :
       return None
    else :
       return complementary

  def getBoxNumber(self):
      return len(self.Objects)

  def getNumbers(self):
      ns = 0
      nb = 0
            
      for m in self.Objects:
        ns += len(m.Surfaces)
        nb += len(m.Definition.elements)

      return ns,nb

  def getBoundPlanes(self):
     Xmid = 0.5*(self.BoundBox.XMin+self.BoundBox.XMax)
     Ymid = 0.5*(self.BoundBox.YMin+self.BoundBox.YMax)
     Zmid = 0.5*(self.BoundBox.ZMin+self.BoundBox.ZMax)
     LX   = (self.BoundBox.ZMin+self.BoundBox.XLength)
     LY   = (self.BoundBox.ZMin+self.BoundBox.YLength)
     LZ   = (self.BoundBox.ZMin+self.BoundBox.ZLength)
     PXMin = GEOUNED_Surface(('Plane',(FreeCAD.Vector(self.BoundBox.XMin,Ymid,Zmid),FreeCAD.Vector( 1,0,0),LY,LZ)),self.BoundBox)
     PXMax = GEOUNED_Surface(('Plane',(FreeCAD.Vector(self.BoundBox.XMax,Ymid,Zmid),FreeCAD.Vector(-1,0,0),LY,LZ)),self.BoundBox)
     PYMin = GEOUNED_Surface(('Plane',(FreeCAD.Vector(Xmid,self.BoundBox.YMin,Zmid),FreeCAD.Vector(0, 1,0),LZ,LX)),self.BoundBox)
     PYMax = GEOUNED_Surface(('Plane',(FreeCAD.Vector(Xmid,self.BoundBox.YMax,Zmid),FreeCAD.Vector(0,-1,0),LZ,LX)),self.BoundBox)
     PZMin = GEOUNED_Surface(('Plane',(FreeCAD.Vector(Xmid,Ymid,self.BoundBox.ZMin),FreeCAD.Vector(0,0, 1),LX,LY)),self.BoundBox)
     PZMax = GEOUNED_Surface(('Plane',(FreeCAD.Vector(Xmid,Ymid,self.BoundBox.ZMax),FreeCAD.Vector(0,0,-1),LX,LY)),self.BoundBox)

     return (PXMin,PXMax,PYMin,PYMax,PZMin,PZMax)

  def __removeExtraComp__(self,Obj,Box,mode='box'):
      reducedSol = []
      reducedDef = BoolSequence(operator='OR')
      if not Obj.Solids: return
      # Compare Solid BoundBox (here Box is BoundBox Object)
      if mode == 'box' :
        for i,sol in enumerate(Obj.Solids):
          if sol.BoundBox.isValid() :
            if Box.intersect(sol.BoundBox) :
              reducedSol.append(sol)
              reducedDef.append(Obj.Definition.elements[i])

      # Compare solid using distToshape (here Box is a Solid Cube object)       
      else :
        for i,sol in enumerate(Obj.Solids):
          dist = Box.distToShape(sol)[0]
          if dist == 0 :
            reducedSol.append(sol)
            reducedDef.append(Obj.Definition.elements[i])

      if len(reducedSol) < len(Obj.Solids):      
        Obj.updateSolids(reducedSol)
        Obj.setDefinition(reducedDef)
      return
            

  def __copyMeta__(self,m):
      solidsCopy = m.Solids[:]
      facesCopy = m.Faces[:]
      Meta = GEOUNED_Solid(m.__id__,solidsCopy) 
      Meta.setDefinition(m.Definition.copy())
      Meta.setFaces(facesCopy)
      if m.IsEnclosure :
          Meta.IsEnclosure = True
          Meta.EnclosureID = m.EnclosureID
      return Meta



    

src/GEOUNED/Void/VoidBoxClass.py
GEOUNED-code/GEOUNED

def assignEnclosure(MetaList,NestedEnclosure):
    """ Assign to all solids the Enclosures ID it belongs to """

    for m in MetaList :
        if m.IsEnclosure: continue
        dep = set()
        outside = True
        for lev,Level in enumerate(reversed(NestedEnclosure)) :     
            for encl in Level :
                inter = m.checkIntersection(encl.CADSolid)
                # CAD solid intersect level i enclosure -> end of nested loops
                # Current loop continue because solid can intersect various enclosure
                if inter == 0 :
                    dep.add(encl.EnclosureID)
                    m.ParentEnclosureID = encl.EnclosureID
                    if lev == len(NestedEnclosure) -1 : 
                       dep.add(-1)
                       m.ParentEnclosureID = -1
                
                # CAD solid inside level i enclosure -> end of current loop go up i-1 level
                elif inter == -1:
                    dep.add(encl.EnclosureID)
                    m.ParentEnclosureID = encl.EnclosureID
                    outside = False
                    break

            # stop loop if CAD solid outside all level i enclosure or intersecting one
            if not outside :
                m.EnclosureList = dep
                break

        if not dep: 
            dep.add(-1)
            m.ParentEnclosureID = -1
        if outside : m.EnclosureList = dep     
    return       
                 
def selectSolids(MetaList,LowLevelEnclosure,Enclosure):
    """This function selects the CAD solids and nested enclosures
 that can intersect with the current nested enclosure.
 For intermediate Levels selected solids are those intersecting i and i+1 level enclosure,
 or fully inside level i enclosure and fully outside i+1 enclosure"""

    newMetaList = LowLevelEnclosure[:]

    if 'BoundBox' in str(Enclosure):
      enclID = -1
    else:
      enclID = Enclosure.EnclosureID

    for m in MetaList:
      if m.IsEnclosure : continue
      if enclID in m.EnclosureList :
          newMetaList.append(m)
     
    return newMetaList

def updateVoidList(offset,voidList,NestedEnclosure,sortEnclosure):

    newVoidList = []
    if NestedEnclosure :
        updateComment = True
    else:    
        updateComment =False
  
    icount = offset + 1
    voids = voidList[0]
    for m in voids :
       m.__id__ = icount
       if updateComment and not sortEnclosure :  m.Comments = m.Comments + '\nLevel 0 void enclosure'
       icount += 1
       m.ParentEnclosureID = -1
       m.EnclosureID = 0
       newVoidList.append(m)
        
    ivoid = 1
    for Level in NestedEnclosure:
        for encl in Level :
           if encl.CellType == 'envelope': continue  
           for m in voidList[ivoid] :
              m.__id__ = icount
              m.ParentEnclosureID = encl.ParentEnclosureID 
              m.EnclosureID = encl.EnclosureID
              if sortEnclosure :
                  m.Comments = m.Comments + '\n{}'.format(encl.Comments)
              else:
                  m.Comments = m.Comments + '\nVoid Enclosure #{}'.format(encl.EnclosureID)
              icount += 1
              newVoidList.append(m)
           ivoid  += 1

    # graveyard Cell
    
    Graveyard = voidList[-1]
    for m in Graveyard :
      m.__id__ = icount
      m.ParentEnclosureID = -1
      m.EnclosureID = 0
      newVoidList.append(m)
      icount += 1

    return newVoidList

src/GEOUNED/Void/voidFunctions.py
GEOUNED-code/GEOUNED

def commentsWrite(name, MetaList):
    '''Function to write in an independent file the comment strings'''
    
    outfile=open(name+'_comments.txt','w',encoding='utf-8')
    for m in MetaList:
        outfile.write(m.Comments+'\n')
    
    outfile.close()


def summaryWrite(name,MetaList):

    outfile=open(name+'_Summary.txt','w',encoding='utf-8')
    header = '  Cell Id{:5s}Mat Id{:6s}Density{:7s}Volume{:5s}Comments\n'.format('','','','')
    outfile.write(header)

    for m in MetaList:
       if m.Void or m.__id__ is None : continue 
       index = m.__id__
       Vol   = m.Volume * 1e-3
       if m.Material == 0:
          line = ' {:>8d}{:3s}{:>8d}{:3s}{:11s}{:3s}{:11.4e}{:3s}{}\n'.format(index,'',0,'','','',Vol,'',m.Comments)
       else:
         if abs(m.Density) < 1e-2 :
            line = ' {:>8d}{:3s}{:>8d}{:3s}{:11.4e}{:3s}{:11.4e}{:3s}{}\n'.format(index,'',m.Material,'',m.Density,'',Vol,'',m.Comments)
         else:
            line = ' {:>8d}{:3s}{:>8d}{:3s}{:11.7f}{:3s}{:11.4e}{:3s}{}\n'.format(index,'',m.Material,'',m.Density,'',Vol,'',m.Comments)

       outfile.write(line)
    outfile.close()

src/GEOUNED/Write/AdditionalFiles.py
GEOUNED-code/GEOUNED
import GEOUNED.Utils.Qform as Qform
from GEOUNED.Utils.BasicFunctions_part1 import isParallel, isOposite
from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf
from GEOUNED.Utils.Options.Classes import Tolerances as tol
from GEOUNED.Write.StringFunctions import remove_redundant

import FreeCAD
import copy
import math
import re

class CardLine :
    def __init__(self,card,linesize=80,tabspace=6,fmt=''):
       self.str = card + ' '
       self.lineSize = linesize
       self.__leftspace__ = linesize - len(self.str)
       self.tabspace = tabspace
       self.tabstr   = ' '*tabspace
       self.fmt = fmt

    def extend(self,dataList):
        line =''
        for data in dataList:
           data_str = '{:{fmt}} '.format(data,fmt=self.fmt)
           if self.__leftspace__ - len(data_str) > 0 :
              line += data_str
              self.__leftspace__ -= len(data_str)
           else:
              self.str += line +'\n'
              line = '{}{}'.format(self.tabstr,data_str)
              self.__leftspace__ =  self.lineSize - len(line)

        self.str += line
    
class CellString :
   def __init__(self,linesize=80,tabspace=6):
       self.str = ''
       self.lineSize = linesize
       self.__leftspace__ = linesize
       self.tabspace = tabspace
       self.tabstr = ' '*tabspace
                   
   def add(self,string):
       self.str += string

   def delLastChar(self):
       self.str = self.str[0:-1]
       self.__leftspace__ += 1

   def wrapLine(self,offset=0):

       self.str = self.str.strip()
       self.str = re.sub(' +'           ,' '        ,self.str)
       self.str = re.sub(' *\( *'       ,'('        ,self.str)
       self.str = re.sub(' *\) *'       ,')'        ,self.str)
       self.str = re.sub(' *: *'        ,':'        ,self.str)
       self.str = re.sub('\)\('         ,') ('      ,self.str)
       self.str = re.sub('(?P<num>\d)\(','\g<num> (',self.str)
       self.str = re.sub('\)(?P<num>\d)',') \g<num>',self.str)
       self.str = re.sub('\)-'          ,') -'      ,self.str)
       
       if len(self.str)+offset <= self.lineSize : return

       newline = ''
       init = 0
       icount = self.lineSize-1 - offset
       lenleft = len(self.str) 

       while True :
           while self.str[icount] not in (')',':',' '):
              icount -= 1

           newline += self.str[init:icount+1]
           lenleft -= icount - init + 1
           init = icount + 1
           icount += self.lineSize - self.tabspace
           if lenleft == 0:
                break
           elif  icount < len(self.str) :
               newline += '\n{}'.format(self.tabstr)
           else:
               newline += '\n{}{}'.format(self.tabstr,self.str[init:])
               break
            
       self.str = newline        

def changeSurfSign(surf,Seq):
    if Seq.level == 0 :
       for i,e in enumerate(Seq.elements):
          if surf == abs(e) : Seq.elements[i] = -Seq.elements[i]
    else:
       for i,e in enumerate(Seq.elements):
          if type(e) is int :
             if surf == abs(e) : Seq.elements[i] = -Seq.elements[i]
          else:
             changeSurfSign(surf,e)

def writeMCNPCellDef(Definition,tabspace=0,offset=0):
       sdef = CellString(tabspace=tabspace)
       strDef = remove_redundant(writeSequenceMCNP(Definition))
       sdef.add(strDef)
       sdef.wrapLine(offset)
       return  sdef.str

def writeOpenMCregion(Definition,Wtype='XML'):
       if Wtype == 'XML':
           return writeSequenceOMCXML(Definition)
       if Wtype == 'PY':
           return writeSequenceOMCPY(Definition)

def writeSequenceMCNP(Seq):
     if Seq.level == 0 :
        if Seq.operator == 'AND' : line = '({})'.format(' '.join(map(str,Seq.elements)))
        else  :                    line = '({})'.format(':'.join(map(str,Seq.elements)))
     else :
        terms = []
        for e in Seq.elements:
           if type(e) is int :
              terms.append(str(e))
           else :
              terms.append(writeSequenceMCNP(e))

        if Seq.operator == 'AND' : line = '({})'.format(' '.join(terms))
        else  :                    line = '({})'.format(':'.join(terms))

     return line

def writeSequenceOMCXML(Seq):
     if Seq.level == 0 :
        if Seq.operator == 'AND' : line = '({})'.format(' '.join(map(str,Seq.elements)))
        else  :                    line = '({})'.format(' | '.join(map(str,Seq.elements)))
     else :
        terms = []
        for e in Seq.elements:
           if type(e) is int :
              terms.append(str(e))
           else :
              terms.append(writeSequenceOMCXML(e))

        if Seq.operator == 'AND' : line = '({})'.format(' '.join(terms))
        else  :                    line = '({})'.format(' | '.join(terms))
     return line

def writeSequenceOMCPY(Seq,prefix='S'):

     strSurf = lambda surf : '-{}{}'.format(prefix,-surf) if surf < 0 else '+{}{}'.format(prefix,surf)

     if Seq.level == 0 :
        if Seq.operator == 'AND' : line = '({})'.format(' & '.join(map(strSurf,Seq.elements)))
        else  :                    line = '({})'.format(' | '.join(map(strSurf,Seq.elements)))
     else :
        terms = []
        for e in Seq.elements:
           if type(e) is int :
              terms.append(strSurf(e))
           else :
              terms.append(writeSequenceOMCPY(e))

        if Seq.operator == 'AND' : line = '({})'.format(' & '.join(terms))
        else  :                    line = '({})'.format(' | '.join(terms))
     return line

def MCNPSurface(id,Type,surf):
    MCNP_def = ''
    if (Type=='Plane'):
        A=surf.Axis.x
        B=surf.Axis.y
        C=surf.Axis.z
        D=surf.Axis.dot(surf.Position)
        if   surf.Axis.isEqual(FreeCAD.Vector(1,0,0),tol.pln_angle) :
           MCNP_def='{:<6d} PX  {:{x}}'.format(id,D/10.0,x=nf.P_xyz)
        elif surf.Axis.isEqual(FreeCAD.Vector(0,1,0),tol.pln_angle) :
           MCNP_def='{:<6d} PY  {:{y}}'.format(id,D/10.0,y=nf.P_xyz)
        elif surf.Axis.isEqual(FreeCAD.Vector(0,0,1),tol.pln_angle) :
           MCNP_def='{:<6d} PZ  {:{z}}'.format(id,D/10.0,z=nf.P_xyz) 
        else:
           MCNP_def='{:<6d} P   {:{abc}} {:{abc}} {:{abc}} {:{d}}'.format(id,A,B,C,D/10.0,abc=nf.P_abc,d=nf.P_d)  
    
    elif (Type == 'Cylinder'):
        Pos = copy.deepcopy(surf.Center)
        Pos.multiply(0.1)
        Dir = copy.deepcopy(surf.Axis)
        Dir.normalize()
        rad = surf.Radius/10.0
        if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)):
          if (Pos.y == 0.0 and Pos.z == 0.0):
             MCNP_def='{:<6d} CX  {:{r}}'.format(id,rad,r=nf.C_r)
          else:
             MCNP_def='{:<6d} C/X  {:{yz}} {:{yz}} {:{r}}'.format(id,Pos.y,Pos.z,rad,yz=nf.C_xyz,r=nf.C_r)
        elif (isParallel(Dir,FreeCAD.Vector(0,1,0),tol.angle)):
          if (Pos.x == 0.0 and Pos.z == 0.0):
             MCNP_def='{:<6d} CY  {:{r}}'.format(id,rad,r=nf.C_r)
          else:
             MCNP_def='{:<6d} C/Y  {:{xz}} {:{xz}} {:{r}}'.format(id,Pos.x,Pos.z,rad,xz=nf.C_xyz,r=nf.C_r)
        elif (isParallel(Dir,FreeCAD.Vector(0,0,1),tol.angle)):
          if (Pos.y == 0.0 and Pos.x == 0.0):
             MCNP_def='{:<6d} CZ  {:{r}}'.format(id,rad,r=nf.C_r)
          else:
             MCNP_def='{:<6d} C/Z  {:{xy}} {:{xy}} {:{r}}'.format(id,Pos.x,Pos.y,rad,xy=nf.C_xyz,r=nf.C_r)
        else:
        # Is not still working fine
          Q=Qform.QFormCyl(Dir,Pos,rad)
          MCNP_def='''\
{:<6d} GQ  {v[0]:{aTof}} {v[1]:{aTof}} {v[2]:{aTof}}
          {v[3]:{aTof}} {v[4]:{aTof}} {v[5]:{aTof}}
          {v[6]:{gToi}} {v[7]:{gToi}} {v[8]:{gToi}}
          {v[9]:{j}} '''.format(id,v=Q,aTof=nf.GQ_1to6,gToi=nf.GQ_7to9,j=nf.GQ_10)
        
        # Si se quiere rcc en vez de Q form
        #    pnt = surf.Center.sub(surf.Axis.multiply(1.0e7)) # mas alla de 100 m
        #    dir = surf.Axis.multiply(1.0e8)
        #    Vx= pnt.x/10.0
        #    Vy= pnt.y/10.0
        #    Vz= pnt.z/10.0
        #    Hx= dir.x/10.0
        #    Hy= dir.y/10.0
        #    Hz= dir.z/10.0
        #    rad=surf.Radius/10.0
        #    MCNP_def='%i  RCC  %13.7E %13.7E %13.7E %13.7E\n       %13.7E %13.7E %13.7E' %(id,Vx,Vy,Vz,Hx,Hy,Hz,rad)
        
    elif (Type == 'Cone'):
        Apex = copy.deepcopy(surf.Apex)
        Apex.multiply(0.1)
        Dir = copy.deepcopy(surf.Axis)
        Dir.multiply(0.1)
        tan = math.tan(surf.SemiAngle)
        X_dir=FreeCAD.Vector(1,0,0)
        Y_dir=FreeCAD.Vector(0,1,0)
        Z_dir=FreeCAD.Vector(0,0,1)
        if (isParallel(Dir,X_dir,tol.angle)):
          sheet=1
          if (isOposite(Dir,X_dir,tol.angle)): sheet=-1
          if (Apex.y == 0.0 and Apex.z == 0.0):
             MCNP_def='{:<6d} KX  {:{x}} {:{t2}} {}'.format(id,Apex.x,tan**2,sheet,x=nf.K_xyz,t2=nf.K_tan2)
          else:
             MCNP_def='{:<6d} K/X  {:{xyz}} {:{xyz}} {:{xyz}} {:{t2}} {}'.format(id,Apex.x,Apex.y,Apex.z,tan**2,sheet,xyz=nf.K_xyz,t2=nf.K_tan2)
        elif (isParallel(Dir,Y_dir,tol.angle)):
          sheet=1
          if (isOposite(Dir,Y_dir,tol.angle)): sheet=-1
          if (Apex.x == 0.0 and Apex.z == 0.0):   
             MCNP_def='{:<6d} KY  {:{y}} {:{t2}} {}'.format(id,Apex.y,tan**2,sheet,y=nf.K_xyz,t2=nf.K_tan2)
          else:
             MCNP_def='{:<6d} K/Y  {:{xyz}} {:{xyz}} {:{xyz}} {:{t2}} {}'.format(id,Apex.x,Apex.y,Apex.z,tan**2,sheet,xyz=nf.K_xyz,t2=nf.K_tan2)
        elif (isParallel(Dir,Z_dir,tol.angle)):
          sheet=1
          if (isOposite(Dir,Z_dir,tol.angle)): sheet=-1
          if (Apex.x == 0.0 and Apex.y == 0.0):
             MCNP_def='{:<6d} KZ  {:{z}} {:{t2}} {}'.format(id,Apex.z,tan**2,sheet,z=nf.K_xyz,t2=nf.K_tan2)
          else:
             MCNP_def='{:<6d} K/Z  {:{xyz}} {:{xyz}} {:{xyz}} {:{t2}} {}'.format(id,Apex.x,Apex.y,Apex.z,tan**2,sheet,xyz=nf.K_xyz,t2=nf.K_tan2)
        else:
          Q=Qform.QFormCone(Dir,Apex,tan)
          MCNP_def='''\
{:<6d} GQ  {v[0]:{aTof}} {v[1]:{aTof}} {v[2]:{aTof}}
          {v[3]:{aTof}} {v[4]:{aTof}} {v[5]:{aTof}}
          {v[6]:{gToi}} {v[7]:{gToi}} {v[8]:{gToi}}
          {v[9]:{j}} '''.format(id,v=Q,aTof=nf.GQ_1to6,gToi=nf.GQ_7to9,j=nf.GQ_10)
            
    elif (Type == 'Sphere'):
     # corresponding logic 
        rad = surf.Radius/10.0
        pnt = surf.Center.multiply(0.1)
        if pnt.isEqual(FreeCAD.Vector(0,0,0),tol.sph_distance):
           MCNP_def='{:<6d} SO  {:{r}}'.format(id,rad,r=nf.S_r)
        else:
           MCNP_def='{:<6d} S  {:{xyz}} {:{xyz}} {:{xyz}} {:{r}}'.format(id,pnt.x,pnt.y,pnt.z,rad,xyz=nf.S_xyz,r=nf.S_r)

    elif (Type == 'Torus'):
        Pos = copy.deepcopy(surf.Center)
        Pos.multiply(0.1)
        Dir = copy.deepcopy(surf.Axis)
        Dir.normalize()
        radMaj = surf.MajorRadius/10.0
        radMin = surf.MinorRadius/10.0
        if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)):
             MCNP_def='''\
{:<6d} TX  {:{xyz}} {:{xyz}} {:{xyz}}
           {:{r}} {:{r}} {:{r}}'''.format(id,Pos.x,Pos.y,Pos.z, \
                                           radMaj,radMin,radMin,\
                                           xyz=nf.T_xyz,r=nf.T_r)
        elif (isParallel(Dir,FreeCAD.Vector(0,1,0),tol.angle)):
             MCNP_def='''\
{:<6d} TY  {:{xyz}} {:{xyz}} {:{xyz}}
           {:{r}} {:{r}} {:{r}}'''.format(id,Pos.x,Pos.y,Pos.z, \
                                           radMaj,radMin,radMin,\
                                           xyz=nf.T_xyz,r=nf.T_r)
        elif (isParallel(Dir,FreeCAD.Vector(0,0,1),tol.angle)):
             MCNP_def='''\
{:<6d} TZ  {:{xyz}} {:{xyz}} {:{xyz}}
           {:{r}} {:{r}} {:{r}}'''.format(id,Pos.x,Pos.y,Pos.z, \
                                           radMaj,radMin,radMin,\
                                           xyz=nf.T_xyz,r=nf.T_r)

    return trim(MCNP_def,80)


def OpenMCSurface(Type,surf,outXML=True,quadricForm=False):
    if (Type=='Plane'):
        A=surf.Axis.x
        B=surf.Axis.y
        C=surf.Axis.z
        D=surf.Axis.dot(surf.Position)
        if   surf.Axis.isEqual(FreeCAD.Vector(1,0,0),tol.pln_angle) :
           if outXML :
              OMCsurf = 'x-plane'
              coeffs = '{:{x}}'.format(D,x=nf.P_xyz)
           else:
              OMCsurf = 'XPlane'
              coeffs = 'x0={}'.format(D)

        elif surf.Axis.isEqual(FreeCAD.Vector(0,1,0),tol.pln_angle) :
           if outXML :
              OMCsurf = 'y-plane'
              coeffs = '{:{x}}'.format(D,x=nf.P_xyz) 
           else:
              OMCsurf = 'YPlane'
              coeffs = 'y0={}'.format(D)

        elif surf.Axis.isEqual(FreeCAD.Vector(0,0,1),tol.pln_angle) :
           if outXML :
              OMCsurf = 'z-plane'
              coeffs = '{:{x}}'.format(D,x=nf.P_xyz)
           else:
              OMCsurf = 'ZPlane'
              coeffs = 'z0={}'.format(D)

        else:
           if outXML :
              OMCsurf = 'plane'
              coeffs = '{:{abc}} {:{abc}} {:{abc}} {:{d}}'.format(A,B,C,D, abc=nf.P_abc, d=nf.P_d)
           else:
              OMCsurf = 'Plane'
              coeffs = 'a={},b={},c={},d={}'.format(A,B,C,D)

    
    elif (Type == 'Cylinder'):
        Pos = surf.Center
        Dir = copy.deepcopy(surf.Axis)
        Dir.normalize()

        if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)):
           if outXML :
              OMCsurf = 'x-cylinder'
              coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.y, Pos.z, surf.Radius, xy=nf.C_xyz, r=nf.C_r) 
           else:
              OMCsurf = 'XCylinder'
              coeffs = 'y0={},z0={},r={}'.format(Pos.y, Pos.z, surf.Radius) 

        elif (isParallel(Dir,FreeCAD.Vector(0,1,0),tol.angle)):
           if outXML :
              OMCsurf = 'y-cylinder'
              coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.z, surf.Radius, xy=nf.C_xyz, r=nf.C_r)
           else:
              OMCsurf = 'YCylinder'
              coeffs = 'x0={},z0={},r={}'.format(Pos.x, Pos.z, surf.Radius) 

        elif (isParallel(Dir,FreeCAD.Vector(0,0,1),tol.angle)):
           if outXML :
              OMCsurf = 'z-cylinder'
              coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.y, surf.Radius, xy=nf.C_xyz, r=nf.C_r) 
           else:
              OMCsurf = 'ZCylinder'
              coeffs = 'x0={},y0={},r={}'.format(Pos.x, Pos.y, surf.Radius) 

        else:
           if outXML :
              OMCsurf = 'quadric'
              Q=Qform.QFormCyl(Dir,Pos,surf.Radius)
              coeffs='{v[0]:{aTof}} {v[1]:{aTof}} {v[2]:{aTof}} {v[3]:{aTof}} {v[4]:{aTof}} {v[5]:{aTof}} {v[6]:{gToi}} {v[7]:{gToi}} {v[8]:{gToi}} {v[9]:{j}}'\
                    .format(v=Q,aTof=nf.GQ_1to6,gToi=nf.GQ_7to9,j=nf.GQ_10) 
           else:
              if quadricForm :
                 OMCsurf = 'Quadric'
                 Q=Qform.QFormCyl(Dir,Pos,surf.Radius)
                 coeffs='a={v[0]},b={v[1]},c={v[2]},d={v[3]},e={v[4]},f={v[5]},g={v[6]},h={v[7]},j={v[8]},k={v[9]}'.format(v=Q) 
              else :
                 OMCsurf = 'Cylinder'
                 coeffs = 'x0={},y0={},z0={},r={},dx={},dy={},dz={}'.format(Pos.x, Pos.y, Pos.z, surf.Radius, Dir.x, Dir.y, Dir.z) 
        
    elif (Type == 'Cone'):
        Apex = surf.Apex
        Dir = copy.deepcopy(surf.Axis)
        Dir.normalize()
        tan  = math.tan(surf.SemiAngle)
        tan2 = tan * tan

        X_dir=FreeCAD.Vector(1,0,0)
        Y_dir=FreeCAD.Vector(0,1,0)
        Z_dir=FreeCAD.Vector(0,0,1)

        if (isParallel(Dir,X_dir,tol.angle)):
           if outXML :
              OMCsurf = 'x-cone'
              coeffs='{:{xyz}} {:{xyz}} {:{xyz}} {:{t2}}'.format(Apex.x, Apex.y, Apex.z, tan2, xyz=nf.K_xyz, t2=nf.K_tan2) 
           else:
              OMCsurf = 'XCone'
              coeffs='x0={},y0={},z0={},r2={}'.format(Apex.x, Apex.y, Apex.z, tan2) 

        elif (isParallel(Dir,Y_dir,tol.angle)):
           if outXML :
              OMCsurf = 'y-cone'
              coeffs='{:{xyz}} {:{xyz}} {:{xyz}} {:{t2}}'.format(Apex.x, Apex.y, Apex.z, tan2, xyz=nf.K_xyz, t2=nf.K_tan2) 
           else:
              OMCsurf = 'YCone'
              coeffs='x0={},y0={},z0={},r2={}'.format(Apex.x, Apex.y, Apex.z, tan2) 

        elif (isParallel(Dir,Z_dir,tol.angle)):
           if outXML :
              OMCsurf = 'z-cone'
              coeffs='{:{xyz}} {:{xyz}} {:{xyz}} {:{t2}}'.format(Apex.x, Apex.y, Apex.z, tan2, xyz=nf.K_xyz, t2=nf.K_tan2)
           else:
              OMCsurf = 'ZCone'
              coeffs='x0={},y0={},z0={},r2={}'.format(Apex.x, Apex.y, Apex.z, tan2) 

        else:
           if outXML :
              OMCsurf = 'quadric'
              Q=Qform.QFormCone(Dir,Apex,tan)
              coeffs='{v[0]:{aTof}} {v[1]:{aTof}} {v[2]:{aTof}} {v[3]:{aTof}} {v[4]:{aTof}} {v[5]:{aTof}} {v[6]:{gToi}} {v[7]:{gToi}} {v[8]:{gToi}} {v[9]:{j}}'\
                    .format(v=Q,aTof=nf.GQ_1to6,gToi=nf.GQ_7to9,j=nf.GQ_10) 
           else:
              if quadricForm :
                 OMCsurf = 'Quadric'
                 Q=Qform.QFormCone(Dir,Apex,tan)
                 coeffs='a={v[0]},b={v[1]},c={v[2]},d={v[3]},e={v[4]},f={v[5]},g={v[6]},h={v[7]},j={v[8]},k={v[9]}'.format(v=Q) 
              else:
                 OMCsurf = 'Cone'
                 coeffs='x0={},y0={},z0={},r2={},dx={},dy={},dz={}'.format(Apex.x, Apex.y, Apex.z, tan2, Dir.x, Dir.y, Dir.z) 
            
    elif (Type == 'Sphere'):
        if outXML :
           OMCsurf = 'sphere'
           coeffs = '{:{xyz}} {:{xyz}} {:{xyz}} {:{r}}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.Radius, xyz=nf.S_xyz, r=nf.S_r) 
        else:
           OMCsurf = 'Sphere'
           coeffs = 'x0={},y0={},z0={},r={}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.Radius) 


    elif (Type == 'Torus'):
        Dir = copy.deepcopy(surf.Axis)
        Dir.normalize()
        if outXML :
           coeffs ='{:{xyz}} {:{xyz}} {:{xyz}} {:{r}} {:{r}} {:{r}}'.format(surf.Center.x, surf.Center.y, surf.Center.z,  \
                                                                     surf.MajorRadius, surf.MinorRadius, surf.MinorRadius,\
                                                                     xyz=nf.T_xyz,r=nf.T_r)     
        else:
           coeffs ='x0={},y0={},z0={},r{},r1={},r2={}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.MajorRadius, surf.MinorRadius, surf.MinorRadius)

        if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)):
             OMCsurf = 'x-torus' if outXML else XTorus
        elif (isParallel(Dir,FreeCAD.Vector(0,1,0),tol.angle)):
             OMCsurf = 'y-torus' if outXML else YTorus
        elif (isParallel(Dir,FreeCAD.Vector(0,0,1),tol.angle)):
             OMCsurf = 'z-torus' if outXML else ZTorus
        else:
             OMCsurf = None

    if outXML : coeffs = ' '.join(coeffs.split())
    return OMCsurf,coeffs

def trim(surfDef,lineLength=80):

    lines = surfDef.split('\n')
    if len(lines) == 1 and len(lines[0]) <= lineLength : 
        return surfDef
  
    longLine = []
    for i,line in enumerate(lines):
       if len(line) > lineLength : 
          longLine.append(i)

    if len(longLine) == 0 : 
        return surfDef

    newDef = ''
    for i,line in enumerate(lines):
       if i in longLine :
          newLine = cutLine(line,lineLength)
       else:
          newLine = line
       newDef += newLine + '\n' 

    return newDef[:-1]


def cutLine(line,lineLength):
    tabNumber = 10 
    while True:
      pos = line.rfind(' ')
      if pos <= lineLength : break

    line1 = line[0:pos]
    line2 = line[pos+1:]

    # second line is only spaces
    if len(line2.strip()) == 0 :
       newLine = line1
    else:
       newLine = '{}\n{: <{n}}{}'.format(line1,'',line2,n=tabNumber)

    return newLine 

src/GEOUNED/Write/Functions.py
GEOUNED-code/GEOUNED
##############################
# Module to write MCNP input #
##############################
from datetime import datetime

from GEOUNED.Utils.Functions import Surfaces_dict
from GEOUNED.Write.Functions import MCNPSurface, changeSurfSign, writeMCNPCellDef,CardLine
from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf
from GEOUNED.Utils.Options.Classes import Tolerances as tol
from GEOUNED.CodeVersion import *
import FreeCAD
import copy
import math
import re

class MCNP_input:
    def __init__(self,Meta,Surfaces,setting) :
       self.Title    = setting['title']
       self.VolSDEF  = setting['volSDEF']
       self.VolCARD  = setting['volCARD']
       self.U0CARD   = setting['UCARD']
       self.dummyMat = setting['dummyMat']
       self.Cells    = Meta
       self.Options  = {'Volume':self.VolCARD,'Particle':('n','p'),'Universe':self.U0CARD}
       self.part     = 'P'

       self.StepFile = setting['stepFile']
       if isinstance(self.StepFile,(tuple,list)):
           self.StepFile = "; ".join(self.StepFile)

       if self.Title == '' :
           self.Title = self.StepFile 
      
       self.__getSurfaceTable__()
       self.__simplifyPlanes__(Surfaces)
       
       self.Surfaces = self.__sortedSurfaces__(Surfaces) 
       self.Materials = set()
       
       return

    def setSDEF(self,data):
    
        if data[0] is not None:
          sphereId = data[0][0]
          radius   = data[0][1]
          wgt      = math.pi*radius*radius*1e-2
          sdef     = 'SDEF PAR={} NRM=-1 SUR={} WGT={:13.7e} DIR=d1\n'.format(self.part,sphereId,wgt)
          SI1      = 'SI1 0 1\n'
          SP1      = 'SP1 -21 1\n'
          self.SDEF_sphere = (sdef,SI1,SP1)
        else:  
          self.SDEF_sphere = None
        xmin,xmax,ymin,ymax,zmin,zmax = data[1]

        sdef = 'SDEF PAR={} X=D1 Y=D2 Z=D3 \n'.format(self.part)
        SI1  = 'SI1 {:13.7e} {:13.7e} \n'.format(xmin*0.1,xmax*0.1)
        SI2  = 'SI2 {:13.7e} {:13.7e} \n'.format(ymin*0.1,ymax*0.1)
        SI3  = 'SI3 {:13.7e} {:13.7e} \n'.format(zmin*0.1,zmax*0.1)
        SP1  = 'SP1 0  1 \n'
        SP2  = 'SP2 0  1 \n'
        SP3  = 'SP3 0  1 \n'
        self.SDEF_box    = (sdef,SI1,SI2,SI3,SP1,SP2,SP3)

    def writeInput(self,filename):
       print ('write MCNP file {}'.format(filename))
       self.inpfile=open(filename,'w',encoding='utf-8')
       self.__write_header__(filename)
       self.__write_cell_block__()
       self.inpfile.write(' \n')
       
       surfaceHeader="""\
C ##########################################################
C                  SURFACE DEFINITION
C ##########################################################
"""
       self.inpfile.write(surfaceHeader)
       self.__write_surface_block__()
       self.inpfile.write(' \n')

       self.__write_source_block__()
         
       self.inpfile.close()
       return                    

    def __write_header__(self,fileout):
 
       version = GEOUNED_Version
       releaseDate = GEOUNED_ReleaseDate
       freeCAD_Version = '{V[0]:}.{V[1]:}.{V[2]:}'.format(V=FreeCAD.Version())

       Header = \
"""{}
C   ______ _______  _____      _     _ __   _ _______ ______  
C  |  ____ |______ |     | ___ |     | | \  | |______ |     \ 
C  |_____| |______ |_____|     |_____| |  \_| |______ |_____/
C Version : {}     {}
C FreeCAD Version : {} \n""".format(self.Title,version,releaseDate,freeCAD_Version) 

       
       Information = \
"""C
C *************************************************************
C Original Step file : {}
C
C Creation Date : {}
C Solid Cells   : {}
C Total Cells   : {}
C Surfaces      : {}
C Materials     : {}
C
C **************************************************************\n""".format(self.StepFile       ,\
                                                                             datetime.now()      ,\
                                                                             self.__solidCells__ ,\
                                                                             self.__cells__      ,\
                                                                             len(self.Surfaces),  \
                                                                             len(self.__materials__) )
       self.inpfile.write(Header)
       self.inpfile.write(Information)
       return
       
    def __write_cell_block__(self):

       for i,cell in enumerate(self.Cells):
          self.__write_cells__(cell)
       return


    def __write_surface_block__(self):

       for surf in self.Surfaces:
          self.__write_surfaces__(surf)


    def __write_cells__(self,cell):

       index = cell.__id__

       # if index is None objet not contain cell definition
       # but a comment to insert between cells
       if index is None :
           comment = self.__commentLine__(cell.Comments)
           self.inpfile.write(comment)
           return

       if cell.Material == 0:
          cellHeader = '{:<5d} {:<5d}  '.format(index,0)
       else:
         self.Materials.add(cell.Material)
         if abs(cell.Density) < 1e-2 :
            cellHeader = '{:<5d} {:<5d} {:11.4e} '.format(index,cell.Material,cell.Density)
         else:
            cellHeader = '{:<5d} {:<5d} {:11.7f} '.format(index,cell.Material,cell.Density)

       mcnpcell = '{}{}\n{}{}'.format( cellHeader                                                   ,\
                                       self.__cellFormat__(cell.Definition,offset=len(cellHeader))  ,\
                                       self.__optionFormat__(cell)                                  ,\
                                       self.__commentFormat__(cell.Comments,cell.MatInfo))
       self.inpfile.write(mcnpcell)
       return


    def __write_surfaces__(self,surface):
       """ Write the surfaces in MCNP format """

       MCNP_def = MCNPSurface(surface.Index, surface.Type, surface.Surf)
       if MCNP_def :
          MCNP_def += '\n'
          self.inpfile.write(MCNP_def) 
       else:
          print('Surface {} cannot be written in MCNP input'.format(surface.Type))
       return 

    def __write_source_block__(self):
       
       if self.SDEF_sphere is None:  return
       
       MODE = 'MODE {}\nVOID \nNPS 1e6\n'.format(self.part)
       if self.dummyMat :
          mat = list(self.Materials)
          mat.sort()
          MATCARD = ''
          for m in mat:
             MATCARD += 'M{:<6d} 1001 1\n'.format(m)           
          Block = MATCARD+'C \n'+MODE
       else:
          Block = MODE
 
       if self.VolSDEF:
          Block += 'PRDMP 2J -1\n'
          for line in self.SDEF_box : 
             Block += 'C ' + line
          for line in self.SDEF_sphere:   
             Block += line
          
          celList,volList = self.__get_solidCellVolume__()
       
          F4Tally = CardLine('F4:{} '.format(self.part))
          F4Tally.extend(celList)
          SD4     = CardLine('SD4  ',fmt='13.7e')
          SD4.extend(volList)

          Block += F4Tally.str+'\n'
          if not self.VolCARD :
             Block += SD4.str
          else :   
             Block += 'C Cell volume normalization is set in cell cards VOL\n'
       else :
          for line in self.SDEF_sphere : 
             Block += 'C ' + line
          for line in self.SDEF_box : 
             Block +=  line
          

       self.inpfile.write(Block)
       
        

    def __cellFormat__(self,Definition,offset=11):
       return writeMCNPCellDef(Definition,tabspace=11,offset=offset)
       

    def __optionFormat__(self,cell):
        
       option = ''
       if self.Options['Volume']:
          if not cell.Void : 
            option ='{:11s}Vol={:e}\n'.format('',cell.Volume*1e-3)
          else:
            option = '{:11s}Vol=1.0\n'.format('')

       option +=  '{:11s}'.format('')          
       for p in self.Options['Particle']:
         if cell.MatInfo == 'Graveyard' :
           option +=  'imp:{}=0     '.format(p)
         else:    
           option +=  'imp:{}=1.0   '.format(p)

       if self.Options['Universe'] is not None:
            option += 'U={}  '.format(self.Options['Universe'])
       option += '\n'

       return option

    def __commentFormat__(self,cComment,mComment=None):
        
       comment = ''
       if mComment :
         mComment = mComment.split('\n')
         for c in mComment :
           if c : comment += '{:11s}${}\n'.format('',c)
           
       if cComment.strip() != '':
          cComment = cComment.strip().split('\n')
          for c in cComment:
            if c: comment += '{:11s}${}\n'.format('',c)
       return comment         

    def __commentLine__(self,lineComment):
       lineComment = lineComment.strip().split('\n')
       comment = ''
       if lineComment :
          comment = 'C \n'
          for c in lineComment:
            if c: comment += 'C {}\n'.format(c)
          comment += 'C \n'
       return comment         

    def __getSurfaceTable__(self):
       self.surfaceTable = {}
       self.__solidCells__ = 0
       self.__cells__      = 0
       self.__materials__ = set()
       
       for i, CellObj in enumerate(self.Cells):
          if CellObj.__id__ is None : continue
          self.__cells__ += 1
          if CellObj.Material != 0 :
             self.__materials__.add(CellObj.Material)

          surf = CellObj.Definition.getSurfacesNumbers()
          if not CellObj.Void : self.__solidCells__ += 1 
          for index in surf : 
              if index in self.surfaceTable.keys() :
                  self.surfaceTable[index].add(i)
              else:
                  self.surfaceTable[index] = {i}
       return

             
    def __simplifyPlanes__(self,Surfaces):
       offset = len(self.Cells)
       keys= self.surfaceTable.keys()

       for p in Surfaces['PX']:
          if p.Surf.Axis[0] < 0 :
            p.Surf.Axis = FreeCAD.Vector(1,0,0)
            self.__changeSurfSign__(p)
 
       for p in Surfaces['PY']:
          if p.Surf.Axis[1] < 0 :
            p.Surf.Axis = FreeCAD.Vector(0,1,0)
            self.__changeSurfSign__(p)
                                           
       for p in Surfaces['PZ']:
          if p.Surf.Axis[2] < 0 :
            p.Surf.Axis = FreeCAD.Vector(0,0,1)
            self.__changeSurfSign__(p)          
       return
    
    def __sortedSurfaces__(self,Surfaces):
        temp = Surfaces_dict(Surfaces)
        surfList = []
        for ind in range(Surfaces.IndexOffset, Surfaces.surfaceNumber + Surfaces.IndexOffset):
           s=temp.getSurface(ind+1)
           if s is not None :  
             surfList.append(s)
             temp.delSurface(ind+1)
        return surfList
    
    def __changeSurfSign__(self,p):

        if p.Index not in self.surfaceTable.keys() :
            print('{} Surface {} not used in cell definition)'.format(p.Type,p.Index),p.Surf.Axis,p.Surf.Position)
            return
        
        for ic in self.surfaceTable[p.Index] :
           surf = self.Cells[ic].Definition.getSurfacesNumbers()
           for s in surf :
             if s == p.Index : 
                changeSurfSign(s, self.Cells[ic].Definition)


    def __get_solidCellVolume__(self):

        solidList = []
        volumeList = []
        for m in self.Cells :
          if m.CellType == 'solid' and m.__id__ is not None:
             solidList.append(m.__id__)
             volumeList.append(m.Volume*1e-3)
                          
        return   solidList, volumeList                        
                          

    

src/GEOUNED/Write/MCNPFormat.py
GEOUNED-code/GEOUNED
##############################
# Module to write MCNP input #
##############################
from datetime import datetime

from GEOUNED.Utils.Functions import Surfaces_dict
from GEOUNED.Write.Functions import OpenMCSurface, changeSurfSign,  writeOpenMCregion,CardLine
from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf
from GEOUNED.Utils.Options.Classes import Options as opt
from GEOUNED.CodeVersion import *
import FreeCAD
import copy
import math
import re

class OpenMC_input:
    def __init__(self,Meta,Surfaces,setting) :

       self.Cells    = Meta

       self.__getSurfaceTable__()
       self.__simplifyPlanes__(Surfaces)
       
       self.Surfaces = self.__sortedSurfaces__(Surfaces) 
       self.Materials = set()

    def writeXML(self,filename):
       print ('write OpenMC xml file {}'.format(filename))
       self.inpfile=open(filename,'w',encoding='utf-8')
       self.__write_xml_header__(filename)

       self.inpfile.write("<geometry>\n")
       self.__write_xml_cell_block__()
       self.inpfile.write(' \n')
       self.__write_xml_surface_block__()
       self.inpfile.write("</geometry>\n")
         
       self.inpfile.close()
       return                    

    def __write_xml_header__(self,fileout):
       Header = "<?xml version='1.0' encoding='UTF-8'?>\n"
       self.inpfile.write(Header)
       return
       
    def __write_xml_cell_block__(self):
       for i,cell in enumerate(self.Cells):
          if  cell.MatInfo == 'Graveyard' : continue
          self.__write_xml_cells__(cell)
       return

    def __write_xml_surface_block__(self):
       for surf in self.Surfaces[:-1]:
          self.__write_xml_surfaces__(surf)
       self.__write_xml_surfaces__(self.Surfaces[-1],boundary=True)

    def __write_xml_cells__(self,cell):
       """ Write the cell in xml OpenMC format """
       index = cell.__id__
       if index is None : return

       if cell.Material == 0: 
          matName = 'void'
       else:
          matName = '{}'.format(cell.Material) 

       OMCcell = '  <cell id="{}" material="{}" region="{}" universe="1"/>\n'.format( \
                                       index,\
                                       matName,\
                                       writeOpenMCregion(cell.Definition,'XML') ) 
       self.inpfile.write(OMCcell)
       return


    def __write_xml_surfaces__(self,surface,boundary=False):
       """ Write the surfaces in xml OpenMC format """

       surfType,coeffs = OpenMCSurface( surface.Type, surface.Surf)

       if not boundary:
          OMCsurf = '  <surface id="{}" type="{}" coeffs="{}" />\n'.format(surface.Index, surfType, coeffs)
       else:
          OMCsurf = '  <surface id="{}" type="{}" coeffs="{}" boundary="vacuum" />\n'.format(surface.Index, surfType, coeffs)

       self.inpfile.write(OMCsurf)
       return 
        
    def writePY(self,filename):
       print ('write OpenMC python script {}'.format(filename))

       # get all the materials present in the model
       for cell in self.Cells:
         if cell.Material != 0: 
           self.Materials.add(cell.Material)

       self.inpfile=open(filename,'w',encoding='utf-8')
       self.__write_py_header__(filename)

       if len(self.Materials) > 0 :
          self.inpfile.write("# Materials setup\n")
          self.__write_py_materials__()
       self.inpfile.write("\n")

       self.inpfile.write("# Surface setup\n")
       self.__write_py_surface_block__()
       self.inpfile.write("\n")

       self.inpfile.write('# Cell definition \n')
       self.__write_py_cell_block__()
         
       self.inpfile.close()
       return                    

    def __write_py_header__(self,fileout):
 
       Header = """\
# openMC geometry script generated by GEOUNED
import openmc

###############################################################################
# Define problem geometry
###############################################################################

"""
       self.inpfile.write(Header)
       return

    def __write_py_materials__(self):
        matList = tuple(sorted(self.Materials))
        strMat = []
        for m in matList:
           material = "M{} = openmc.Material(name='M{}')\n".format(m,m)
           self.inpfile.write(material)
           strMat.append('M{}'.format(m))

        collect = 'materials = openmc.Materials([{}])\n'.format(', '.join(strMat))
        self.inpfile.write(collect)
        self.inpfile.write('materials.export_to_xml()\n')


    def __write_py_surface_block__(self):

       for surf in self.Surfaces[:-1]:
          self.__write_py_surfaces__(surf)
       self.__write_py_surfaces__(self.Surfaces[-1],boundary=True)


    def __write_py_surfaces__(self,surface,boundary=False):
       """ Write the surfaces in xml OpenMC format """

       surfType,coeffs = OpenMCSurface(surface.Type, surface.Surf, outXML = False, quadricForm = opt.quadricPY)

       if not boundary:
          OMCsurf = 'S{} = openmc.{}({})\n'.format(surface.Index, surfType, coeffs)
       else:
          OMCsurf = 'S{} = openmc.{}({}, boundary_type="vacuum")\n'.format(surface.Index, surfType, coeffs)

       self.inpfile.write(OMCsurf)
       return 

    def __write_py_cell_block__(self):

       cellNames = []
       for i,cell in enumerate(self.Cells):
          if  cell.MatInfo == 'Graveyard' : continue
          self.__write_py_cells__(cell)
          if cell.__id__ is None : continue
          cellNames.append('C{}'.format(cell.__id__))

       geometry = "\ngeometry = openmc.Geometry([{}])\ngeometry.export_to_xml()\n".format(', '.join(cellNames)) 

       self.inpfile.write(geometry)
       return


    def __write_py_cells__(self,cell):
       """ Write the cell in xml OpenMC format """
       index = cell.__id__
       if index is None : return

       if cell.Material == 0: 
          OMCcell = 'C{} = openmc.Cell(region={})\n'.format(index, writeOpenMCregion(cell.Definition,'PY') ) 
       else:
          matName = 'M{}'.format(cell.Material) 
          OMCcell = 'C{} = openmc.Cell(fill={}, region={})\n'.format(index, matName, writeOpenMCregion(cell.Definition,'PY') ) 
       self.inpfile.write(OMCcell)
       return


    def __getSurfaceTable__(self):
       self.surfaceTable = {}
       self.__solidCells__ = 0
       self.__cells__      = 0
       self.__materials__ = set()
       
       for i, CellObj in enumerate(self.Cells):
          if CellObj.__id__ is None : continue
          self.__cells__ += 1
          if CellObj.Material != 0 :
             self.__materials__.add(CellObj.Material)

          surf = CellObj.Definition.getSurfacesNumbers()
          if not CellObj.Void : self.__solidCells__ += 1 
          for index in surf : 
              if index in self.surfaceTable.keys() :
                  self.surfaceTable[index].add(i)
              else:
                  self.surfaceTable[index] = {i}
       return

             
    def __simplifyPlanes__(self,Surfaces):
       offset = len(self.Cells)
       keys= self.surfaceTable.keys()

       for p in Surfaces['PX']:
          if p.Surf.Axis[0] < 0 :
            p.Surf.Axis = FreeCAD.Vector(1,0,0)
            self.__changeSurfSign__(p)
 
       for p in Surfaces['PY']:
          if p.Surf.Axis[1] < 0 :
            p.Surf.Axis = FreeCAD.Vector(0,1,0)
            self.__changeSurfSign__(p)
                                           
       for p in Surfaces['PZ']:
          if p.Surf.Axis[2] < 0 :
            p.Surf.Axis = FreeCAD.Vector(0,0,1)
            self.__changeSurfSign__(p)          
       return
    
    def __sortedSurfaces__(self,Surfaces):
        temp = Surfaces_dict(Surfaces)
        surfList = []
        for ind in range(Surfaces.IndexOffset, Surfaces.surfaceNumber + Surfaces.IndexOffset):
           s=temp.getSurface(ind+1)
           if s is not None :  
             surfList.append(s)
             temp.delSurface(ind+1)
        return surfList
    
    def __changeSurfSign__(self,p):

        if p.Index not in self.surfaceTable.keys() :
            print('{} Surface {} not used in cell definition)'.format(p.Type,p.Index),p.Surf.Axis,p.Surf.Position)
            return
        
        for ic in self.surfaceTable[p.Index] :
           surf = self.Cells[ic].Definition.getSurfacesNumbers()
           for s in surf :
             if s == p.Index : 
                changeSurfSign(s, self.Cells[ic].Definition)



    

src/GEOUNED/Write/OpenMCFormat.py
GEOUNED-code/GEOUNED
import re


#########################################
# define patterns to be found in string #
#########################################


# used in card_split
celmat=re.compile(r"(?P<cel>^ *\d+) +(?P<scnd>(\d+|like))",re.I)      # identify first two entries on cell card (cell name, material)
grlnumber=re.compile(r"[-+]?(\d+\.\d+|\.\d+|\d+\.?)(e[+-]\d+)?",re.I) # identify a general number form signed integer, float or exponential
param=re.compile(r"((^|\n {5})[\(\):\-\+\d+\.\# ]*)([\*a-z])",re.I)   # identity begining of the paramter part of the cell card
likebut=re.compile(r"but",re.I)                                       # identify likebut  card
trans=re.compile(r"trcl|fill= *\d+[ c\$\n]*\(,",re.I)                 # identify tranformed card

# user in get_stat function
reword=re.compile(r"(\d+|\(|\)|:|\#)")                    # word identification in cell line
compcell=re.compile(r"\#\d")                              # identify hashcell complement operator

# used in Complementary operator function
number=re.compile(r"(?P<number>[-+]?\d+)")                                     # signed (+-) (or not) numbers
# leftp=re.compile(r"^ *(?P<left>[-\d\(\#])",re.M)                               # identify first valid character
leftp=re.compile(r"(?P<left>[-+\d\(\#])",re.I)                                  # identify first valid character
rightp=re.compile(r"(?P<right>[ c\$\n]*$)",re.I)                               # identify last valid character
#interblk=re.compile(r"(?P<previous>\d)(?P<next>(( +| *(\$)?\n(C\n)* *)[-+]?\d))") # two numbers separated by blank (or newline or comments)
#intercls=re.compile(r"(?P<previous>\))(?P<next>(( *| *(\$)?\n(C\n)* *)[-+]?\d))") # closed parenthesis followed by number
#interopn=re.compile(r"(?P<previous>\d)(?P<next>(( *| *(\$)?\n(C\n)* *)\())")   # number followed by opened parenthesis
#intercop=re.compile(r"(?P<previous>\))(?P<next>(( *| *(\$)?\n(C\n)* *)\())")   # closed parenthesis followed by opened parenthesis
interblk=re.compile(r"(?P<previous>\d)(?P<next>(( +| *((\n *)?\$|\nC)*\n *)[-+]?\d))") # two numbers separated by blank (or newline or comments)
intercls=re.compile(r"(?P<previous>\))(?P<next>(( *| *((\n *)?\$|\nC)*\n *)[-+]?\d))") # closed parenthesis followed by number
interopn=re.compile(r"(?P<previous>\d)(?P<next>(( *| *((\n *)?\$|\nC)*\n *)\())")   # number followed by opened parenthesis
intercop=re.compile(r"(?P<previous>\))(?P<next>(( *| *((\n *)?\$|\nC)*\n *)\())")   # closed parenthesis followed by opened parenthesis
colonamp=re.compile(r"[:&]")                                                   # colon or ampersand

# used for remove redundant parenthesis function
mostinner=re.compile(r"\([^\(^\)]*\)")                                      # identify most inner parentheses
bracketsemi=re.compile(r"[\]\[;]")                                          # square bracket or semicolon
blnkline=re.compile(r"^ *\n",re.M)                                          # identify blank line
contline=re.compile(r"\n {0,4}(?P<start>[^c^ ])",re.I)                      # identify character other than 'C' in fisrt 5 columns
comdollar=re.compile(r"\n(?P<blnk> *)\$")                                   # identify dollar on 'blank line'
startgeom=re.compile(r"(?P<previous>^ *)(?P<start>[\-\+\d])")               # identify beginning of the geomtric part
endgeom=re.compile(r"(?P<last>\d)(?P<next> *((\n *)?\$|\nc)?(\n *)?$)",re.I)    # identify end of the geomtric part
#endgeom=re.compile(r"(?P<last>\d)(?P<next> *(\$|\nc)?(\n *)?$)",re.I)                      # identify end of the geomtric part

# other
rehash=re.compile(r"# *(\d+|\()")                                             # find beginning of complementary operator (both cell and surf)
parent=re.compile(r"[\(|\)]")                                               # position of open and close parenthesis (get_hashcell)
gline=re.compile(r"(^ ?[\(\):\-\+\d+\.\# ]+|\n {5}[\(\):\-\+\d+\.\# ]+)",re.I)  # valid geometric part of the line       (remove/restore_comments)
comments=re.compile(r"((\n *)?\$|\n *c)",re.I)                               # begining of comment part               (remove/restore_comments)
#comments=re.compile(r"\$|\n *c",re.I)                               # begining of comment part               (remove/restore_comments)
lastCR = re.compile(r"\n *$")                                        #last newline "\n" on line string

      
# restore curve parentheses and colon characters
def reverse_repl(m):
    symb=m.group(0)
    if symb == '[' :
      return '('
    elif symb == ']' :
      return ')'
    else :
      return ':'

def countP(string):
      lp=string.count('(')
      rp=string.count(')')
      return (lp,rp)

def redundant(m,geom):
   """ check if the inner parentheses are redundant """
   term = m.group()

   # Find first valid character at the left of the  parenthese
   leftOK= True
   left = m.start()-1
   while left > -1:
       if geom[left] in ('\n','C','$',' '):
          left -= 1
       else:
          if geom[left] not in ('(',':') : leftOK  = False
          break

  # check if no ':' (or) are inside the parenthese
  # if not, parentheses are redundants
   if (term.find(':') == -1) : return True

  # Find first valid character at the right of the  parenthese
   rightOK= True
   right = m.end()
   while right < len(geom)  :
       if geom[right] in ('\n','C','$',' '):
          right += 1
       else:
          if geom[right] not in (')',':') : rightOK  = False
          break

  # if parentheses are like:
  # {( or : } ( ....... ) {) or :}
  # parentheses are redundants

   if leftOK and rightOK :
       return True
   else:
       return False
    
def remove_redundant(geom):
   """ return cell without redundant parenthesis """

   porg=countP(geom)
   # Loop until no redundant parentheses are found
   cont = True
   while cont:
     # Loop over most inner parentheses
      pos = 0
      cont = False
      while True :
         m = mostinner.search(geom,pos)
         if not m : break
         cont = True
         if redundant(m,geom):
            # remove redundant parentheses
            geom = geom[:m.start()]+ ' ' + geom[m.start()+1:m.end()-1]+ ' ' + geom[m.end():]
         else:
            # replace no redundant parentheses by [] and : by ;
            term = geom[m.start()+1:m.end()-1].replace(':',';')
            geom = geom[:m.start()] + '[' + term + ']' + geom[m.end():]
         pos = m.end()

   # restore curved parenthesis and colon
   geom=re.sub(bracketsemi,reverse_repl,geom)

   # remove possible blank line
   geom=re.sub(blnkline,'',geom)

   # ensure 5 blanks continous line
   geom=re.sub(contline, r'\n     \g<start>',geom)

   # remove last newline at the end of the line string
   geom=re.sub(lastCR,'',geom)

   return geom

src/GEOUNED/Write/StringFunctions.py
GEOUNED-code/GEOUNED
import GEOUNED.Write.AdditionalFiles as OutFiles
from GEOUNED.Write.MCNPFormat        import MCNP_input
from GEOUNED.Write.OpenMCFormat      import OpenMC_input 

def writeGeometry(UniverseBox,MetaList,Surfaces,code_setting):

   baseName  = code_setting['geometryName']

   # write cells comments in file
   if code_setting['cellCommentFile']:
       OutFiles.commentsWrite(baseName,MetaList)
   if code_setting['cellSummaryFile']:
       OutFiles.summaryWrite(baseName,MetaList)

   
   if 'mcnp' in code_setting['outFormat']:
       mcnpFilename = baseName + '.mcnp'
       outBox = (UniverseBox.XMin,UniverseBox.XMax,UniverseBox.YMin,UniverseBox.YMax,UniverseBox.ZMin,UniverseBox.ZMax)
       if code_setting['voidGen'] :
          outSphere = (Surfaces['Sph'][-1].Index,Surfaces['Sph'][-1].Surf.Radius)
       else:
          outSphere = None       

       MCNPfile = MCNP_input(MetaList,Surfaces,code_setting)
       MCNPfile.setSDEF((outSphere,outBox))
       MCNPfile.writeInput(mcnpFilename)

   if 'openMC_XML' in code_setting['outFormat'] or \
      'openMC_PY'  in code_setting['outFormat'] :
       OMCFile = OpenMC_input(MetaList,Surfaces,code_setting)

   if 'openMC_XML' in code_setting['outFormat']:
       omcFilename = baseName + '.xml'
       OMCFile.writeXML(omcFilename)
       

   if 'openMC_PY' in code_setting['outFormat']:
       omcFilename = baseName + '.py'
       OMCFile.writePY(omcFilename)

src/GEOUNED/Write/WriteFiles.py
GEOUNED-code/GEOUNED
#Init file of GEOUNED module
#

# We load the STEP and the materials
import sys
import FreeCAD,Part
import configparser

from os import path,mkdir
from datetime import datetime


import GEOUNED.LoadFile.LoadSTEP as Load
import GEOUNED.Decompose.Decom_one as Decom
import GEOUNED.Utils.Functions as UF
import GEOUNED.Conversion.CellDefinition as Conv
from GEOUNED.Write.Functions import writeMCNPCellDef
from GEOUNED.Write.WriteFiles import writeGeometry
from GEOUNED.CodeVersion import *
from GEOUNED.Utils.Options.Classes import Tolerances, MCNP_numeric_format, Options 
from GEOUNED.Utils.BooleanSolids import buildCTableFromSolids

import GEOUNED.Void.Void as Void


class GEOUNED() :

   def __init__(self,title='Geouned conversion'):
      """ """
      self.__dict__ = dict() 
      self.__dict__['stepFile']     = ''
      self.__dict__['geometryName'] = ''
      self.__dict__['matFile']      = ''
      self.__dict__['outFormat']    = ('mcnp',)
      self.__dict__['title']        = title
      self.__dict__['voidGen']      = True
      self.__dict__['debug']        = False
      self.__dict__['compSolids']   = True
      self.__dict__['volSDEF']      = False
      self.__dict__['dummyMat']     = False
      self.__dict__['volCARD']      = True
      self.__dict__['UCARD']        = None
      self.__dict__['simplify']     = 'No'
      self.__dict__['cellRange']    = []
      self.__dict__['exportSolids'] = ''
      self.__dict__['minVoidSize']  = 200  # units mm
      self.__dict__['maxSurf']      = 50
      self.__dict__['maxBracket']   = 30
      self.__dict__['voidMat']      = []
      self.__dict__['voidExclude']  = []
      self.__dict__['startCell']    = 1
      self.__dict__['startSurf']    = 1
      self.__dict__['cellCommentFile'] = False
      self.__dict__['cellSummaryFile'] = True
      self.__dict__['sortEnclosure'] = False

   def SetOptions(self):
      toleranceKwrd = ( 'relativeTolerance', 'relativePrecision', 'singleValue', 'generalDistance', 'generalAngle',
                        'planeDistance', 'planeAngle', 'cylinderDistance', 'cylinderAngle', 'sphereDistance', 'coneDistance',
                        'coneAngle', 'torusDistance', 'torusAngle')
      numericKwrd = ('P_abc', 'P_d', 'P_xyz', 'S_r', 'S_xyz', 'C_r', 'C_xyz', 'K_tan2', 'K_xyz', 'T_r', 'T_xyz', 'GQ_1to6', 'GQ_7to9', 'GQ_10' )
      tolKwrdEquiv = {'relativeTolerance':'relativeTol' , 'relativePrecision':'relativePrecision', 'singleValue':'value', 'generalDistance':'distance',
                      'generalAngle': 'angle', 'planeDistance':'pln_distance', 'planeAngle':'pln_angle', 'cylinderDistance':'cyl_distance', 
                      'cylinderAngle':'cyl_angle', 'sphereDistance':'sph_distance', 'coneDistance':'kne_distance', 'coneAngle': 'kne_angle',
                      'torusDistance':'tor_distance', 'torusAngle':'tor_angle'}

      config = configparser.ConfigParser()
      config.optionxform = str
      config.read(self.__dict__['title'])
      for section in config.sections():
         if section == 'Files':
            for key in config['Files'].keys() :
                 if key in ('geometryName','matFile','title') :
                     self.set(key, config.get('Files',key))   

                 elif key == 'stepFile':
                     value = config.get('Files',key).strip()
                     if value[0] in ('(','[')  and value[-1] in (']',')') :
                        data=value[1:-1].split(',')
                        self.set(key,data)
                     else :
                        self.set(key, value)   

                 elif key == 'outFormat':
                     raw = config.get('Files',key).strip()
                     values = tuple( x.strip() for x in raw.split(',') )
                     outFormat = []
                     for v in values:
                        if   v.lower() == 'mcnp'       : outFormat.append('mcnp')
                        elif v.lower() == 'openmc_xml' : outFormat.append('openMC_XML')
                        elif v.lower() == 'openmc_py'  : outFormat.append('openMC_PY')
                     self.set(key, tuple(outFormat))   
                     

         elif section == 'Parameters':
            for key in config['Parameters'].keys() :
                 if key in ('voidGen','debug','compSolids','volSDEF','volCARD',
                            'dummyMat','cellSummaryFile','cellCommentFile','sortEnclosure') :
                     self.set(key, config.getboolean('Parameters',key))   
                 elif key in ('minVoidSize','maxSurf','maxBracket','startCell','startSurf') :
                     self.set(key, config.getint('Parameters',key))   
                 elif key in ('exportSolids','UCARD','simplify') :
                     self.set(key, config.get('Parameters',key))   
                 elif key == 'voidMat':
                     value = config.get('Parameters',key).strip()
                     data=value[1:-1].split(',')
                     self.set(key,(int(data[0]),float(data[1]),data[2]))
                 else:
                     value = config.get('Parameters',key).strip()
                     data=value[1:-1].split(',')
                     self.set(key,tuple(map(int,data)))

         elif section == 'Options':
            for key in config['Options'].keys() :
               if key in ('forceCylinder','newSplitPlane','delLastNumber','verbose','quadricPY') :
                   setattr(Options,key, config.getboolean('Options',key))
               elif key in ('enlargeBox','nPlaneReverse','splitTolerance'):
                   setattr(Options,key, config.getfloat('Options',key))
         
         elif section == 'Tolerances':
            for key in config['Tolerances'].keys() :
                 if key == 'relativeTolerance' :
                     setattr(Tolerances,key, config.getboolean('Tolerances',key))
                 elif key in toleranceKwrd : setattr(Tolerances,tolKwrdEquiv[key], config.getfloat('Tolerances',key))   

         elif section == 'MCNP_Numeric_Format':
            for key in config['MCNP_Numeric_Format'].keys() :
                 if key in numericKwrd : setattr(MCNP_numeric_format,key, config.get('MCNP_Numeric_Format',key))   
             
         else:
            print('bad section name : {}'.format(section))

      if self.__dict__['geometryName'] == '' :
         self.__dict__['geometryName'] = self.__dict__['stepFile'][:-4]
      print(self.__dict__)

   def set(self,kwrd,value):

       if kwrd not in self.__dict__.keys():
          print('Bad entry : {}'.format(kwrd))
          return

       if kwrd == 'stepFile' :
          if isinstance(value,(list,tuple)) :
             for v in value :
               if not isinstance(v,str) :
                  print('elemt in {} list should be string'.format(kwrd))
                  return
          elif not isinstance(value,str) :
                print('{} should be string or tuple of strings'.format(kwrd))
                return

       elif kwrd == 'UCARD' :
           if value == 'None' :
              value = None
           elif value.isdigit() :
              value = int(value)
           else:
              print('{} value should be None or integer'.format(kwrd))
              return
       elif kwrd == 'outFormat' :
           if len(value) == 0: return
       elif kwrd in ('geometryName','matFile','exportSolids'):
           if not isinstance(value,str) :
              print('{} value should be str instance'.format(kwrd))
              return   
       elif kwrd in ('cellRange','voidMat','voidExclude'):
           if not isinstance(value,(list,tuple)) :
              print('{} value should be list or tuple'.format(kwrd))
              return
       elif kwrd in ('minVoidSize','maxSurf','maxBracket','startCell','startSurf'):
           if not isinstance(value,int) :
              print('{} value should be integer'.format(kwrd))
              return
       elif kwrd in ('voidGen','debug','compSolids','simplifyCTable','volSDEF','volCARD', 
                     'dummyMat','cellSummaryFile','cellCommentFile','sortEnclosure') :
           if not isinstance(value,bool) :
              print('{} value should be boolean'.format(kwrd))
              return    

       self.__dict__[kwrd] = value
       if kwrd == 'stepFile' and  self.__dict__['geometryName'] == '' :
            if isinstance(value,(tuple,list)):
               self.__dict__['geometryName'] == 'joined_step_files' 
            else: 
               self.__dict__['geometryName'] == value[:-4]

             
   def Start(self):

       print('start')
       FreeCAD_Version = '{V[0]:}.{V[1]:}.{V[2]:}'.format(V=FreeCAD.Version())
       print('GEOUNED version {} {} \nFreeCAD version {}'.format(GEOUNED_Version,GEOUNED_ReleaseDate,FreeCAD_Version))

       code_setting = self.__dict__
       if code_setting is None:
          print('Cannot run the code. Input are missing')
          exit()
       if code_setting['stepFile'] == '':
          print('Cannot run the code. Step file name is missing')
          exit()
          
       stepfile = code_setting['stepFile']
       matfile  = code_setting['matFile']
       startTime = datetime.now()

       if isinstance(stepfile,(list,tuple)):
          MetaChunk      = []
          EnclosureChunk = []
          for stp in stepfile :
             print('read step file : {}'.format(stp))   
             Meta,Enclosure = Load.LoadCAD(stp,matfile)
             MetaChunk.append(Meta)
             EnclosureChunk.append(Enclosure)
          MetaList = joinMetaLists(MetaChunk)
          EnclosureList = joinMetaLists(EnclosureChunk)
       else:
          print('read step file : {}'.format(stepfile)) 
          MetaList,EnclosureList = Load.LoadCAD(stepfile,matfile,code_setting['voidMat'],code_setting['compSolids'])

       print('End of loading phase')
       tempstr1 = str(datetime.now()-startTime)
       print(tempstr1)
       tempTime = datetime.now()

       # Select a specific solid range from original STEP solids
       if code_setting['cellRange'] :
           MetaList = MetaList[code_setting['cellRange'][0]:code_setting['cellRange'][1]]

       # export in STEP format solids read from input file
       # terminate excution
       if code_setting['exportSolids'] != '':    
          solids=[]
          for m in MetaList:
              if m.IsEnclosure: continue 
              solids.extend(m.Solids)
          Part.makeCompound(solids).exportStep(code_setting['exportSolids'])
          print ('Solids exported in file :{}'.format(code_setting['exportSolids']))
          print ('GEOUNED Finish. No solid translation performed.')
          sys.exit()

       # set up Universe
       if EnclosureList :
          UniverseBox = getUniverse(MetaList+EnclosureList)
       else:
          UniverseBox = getUniverse(MetaList)
       Comsolids= []

       surfOffset = code_setting['startSurf'] - 1
       Surfaces    = UF.Surfaces_dict(offset=surfOffset)


       # decompose all solids in elementary solids (convex ones)
       warningSolidList = DecomposeSolids(MetaList,Surfaces,UniverseBox,code_setting,True)

       # decompose Enclosure solids
       if code_setting['voidGen'] and EnclosureList :
          warningEnclosureList = DecomposeSolids(EnclosureList,Surfaces,UniverseBox,code_setting,False)
           
       print('End of decomposition phase')
       tempstr2 = str(datetime.now()-tempTime)
       print(tempstr2)

       # start Building CGS cells phase   
       tempTime0 = datetime.now()
       warnSolids     = []
       warnEnclosures = []
 
       for j,m in enumerate(MetaList):
           if m.IsEnclosure : continue
           print('Building cell: ', j)
           Conv.cellDef(m,Surfaces,UniverseBox)
           if j in warningSolidList:
               warnSolids.append(m)
           if not m.Solids:
              print('none',j,m.__id__)
              print(m.Definition)

       #  building enclosure solids       

       if code_setting['voidGen'] and EnclosureList :
           for j,m in enumerate(EnclosureList):
             print('Building Enclosure Cell: ', j)
             Conv.cellDef(m,Surfaces,UniverseBox)
             if j in warningEnclosureList:
                warnEnclosures.append(m)
        

       tempTime1 = datetime.now()

       printWarningSolids(warnSolids,warnEnclosures)

       # void generation phase
       MetaVoid = []
       if code_setting['voidGen'] :
         print('Build Void')
         print(code_setting['voidExclude'])
         if not code_setting['voidExclude']:
            MetaReduced = MetaList            
         else :
            MetaReduced = excludeCells(MetaList,code_setting['voidExclude'])

         if MetaList :
            init = MetaList[-1].__id__ -len(EnclosureList)
         else:
            init = 0
         MetaVoid = Void.voidGeneration(MetaReduced,EnclosureList,Surfaces,UniverseBox,code_setting,init)

       if code_setting['simplify'] == 'full' :
           Surfs = {}
           for lst in Surfaces.values():
              for s in lst:
                Surfs[s.Index] = s

           for c in MetaList:
              if c.Definition.level == 0 or c.IsEnclosure: continue
              print('simplify cell',c.__id__)
              Box =  getBox(c)
              CT = buildCTableFromSolids(Box,(c.Surfaces,Surfs),option='full')
 
                   
              c.Definition.simplify(CT)
              res = c.Definition.clean()
              if res is not None:
                  print('unexpected constant cell {} :{}'.format(cell.__id__,res)) 



       tempTime2 = datetime.now()
       print('build Time:',tempTime2-tempTime1)
 
       print(datetime.now()-startTime)

       cellOffSet = code_setting['startCell'] - 1
       if EnclosureList and code_setting['sortEnclosure'] :
          # sort group solid cell / void cell sequence in each for each enclosure
          # if a solid belong to several enclosure, its definition will be written 
          # for the highest enclosure level or if same enclosure level in the first 
          # enclosure found
          MetaList = sortEnclosure(MetaList,MetaVoid,cellOffSet) 
       else:
          # remove Null Cell and apply cell numbering offset
          deleted = []
          icount = 0
          for i,m in enumerate(MetaList):
              if m.NullCell or m.IsEnclosure :
                deleted.append(i)
                icount += 1
                continue
              m.__id__ += cellOffSet - icount
          for i in reversed(deleted):
             del MetaList[i]       

          lineComment = """\
##########################################################
             VOIDS 
##########################################################"""
          mc = UF.GEOUNED_Solid(None)
          mc.Comments = lineComment
          MetaList.append(mc)

          deleted = []
          icount = 0
          for i,m in enumerate(MetaVoid):
              if m.NullCell :
                deleted.append(i)
                icount += 1
                continue
              m.__id__ += cellOffSet - icount
          for i in reversed(deleted):
             del MetaVoid[i]       

          MetaList.extend(MetaVoid)

       # write outputformat input
       writeGeometry(UniverseBox,MetaList,Surfaces,code_setting)
   
       print('End of MCNP translation phase')

       print('Process finished')
       print(datetime.now()-startTime)
    
       print('Translation time of solid cells', tempTime1-tempTime0)
       print('Translation time of void cells', tempTime2-tempTime1)


def DecomposeSolids(MetaList,Surfaces,UniverseBox,setting,meta):
    totsolid = len(MetaList)
    warningSolids = []
    for i,m in enumerate(MetaList):
        if meta and m.IsEnclosure: continue
        print('Decomposing solid: {}/{} '.format(i,totsolid))
        if setting['debug'] :
            print(m.Comments)
            if m.IsEnclosure :
               m.Solids[0].exportStep('origEnclosure_{}.stp'.format(i))  
            else:    
               m.Solids[0].exportStep('origSolid_{}.stp'.format(i))
        
        comsolid,err=Decom.SplitSolid(Part.makeCompound(m.Solids),UniverseBox)

        if err != 0 :
           if not path.exists('Suspicious_solids') :
              mkdir("Suspicious_solids")
           if m.IsEnclosure:   
              m.Solids[0].exportStep(u'Suspicious_solids/Enclosure_original_{}.stp'.format(i))
              comsolid.exportStep(   u'Suspicious_solids/Enclosure_split_{}.stp'.format(i))
           else: 
              m.Solids[0].exportStep(u'Suspicious_solids/Solid_original_{}.stp'.format(i))
              comsolid.exportStep(   u'Suspicious_solids/Solid_split_{}.stp'.format(i))

           warningSolids.append(i)
           
        if setting['debug'] :
            if m.IsEnclosure :
                comsolid.exportStep('compEnclosure_{}.stp'.format(i))
            else:
                comsolid.exportStep('compSolid_{}.stp'.format(i))
        Surfaces.extend(Decom.ExtractSurfaces(comsolid,'All',UniverseBox,MakeObj=True))
        m.setCADSolid()
        m.updateSolids(comsolid.Solids)

    return warningSolids   


def getBox(comp):
    bb=FreeCAD.BoundBox(comp.BoundBox)
    apexList = findCone(comp.Faces)
    if apexList :
       xMin,xMax,yMin,yMax,zMin,zMax = bb.XMin, bb.XMax, bb.YMin, bb.YMax, bb.ZMin, bb.ZMax
       for apex in apexList:
          if apex.x < xMin   : xMin = apex.x
          elif apex.x > xMax : xMax = apex.x
          if apex.y < yMin   : yMin = apex.y
          elif apex.y > yMax : yMax = apex.y
          if apex.z < zMin   : zMin = apex.z
          elif apex.z > zMax : zMax = apex.z
       xLength = xMax-xMin + Options.enlargeBox
       yLength = yMax-yMin + Options.enlargeBox
       zLength = zMax-zMin + Options.enlargeBox
       xMin -= 0.5*Options.enlargeBox
       yMin -= 0.5*Options.enlargeBox
       zMin -= 0.5*Options.enlargeBox
    else:
       bb.enlarge(Options.enlargeBox)
       xMin,yMin,zMin = bb.XMin, bb.YMin, bb.ZMin
       xLength,yLength,zLength = bb.XLength,  bb.YLength,  bb.ZLength 
       
    return Part.makeBox( xLength, yLength, zLength,
                         FreeCAD.Vector( xMin, yMin, zMin),
                         FreeCAD.Vector(0,0,1))

def findCone(Faces):
   coneObj = []
   for list1 in Faces:
     for f in list1:
        if type(f) is list :
           for ff in f :
              if ff is not None :
                 if "Cone" in str(ff.Surface) : coneObj.append(ff)
        else:
           if "Cone" in str(f.Surface) : coneObj.append(f)

   apexList = []
   for c in coneObj :
      apexList.append(c.Surface.Apex)
   return apexList

def getUniverse(MetaList):
      d = 10
      Box  = MetaList[0].BoundBox
      xmin = Box.XMin
      xmax = Box.XMax
      ymin = Box.YMin
      ymax = Box.YMax
      zmin = Box.ZMin
      zmax = Box.ZMax
      for m in  MetaList[1:]:
       # MIO. This was removed since in HELIAS the enclosure cell is the biggest one
       # if m.IsEnclosure: continue
        xmin = min(m.BoundBox.XMin,xmin)
        xmax = max(m.BoundBox.XMax,xmax)
        ymin = min(m.BoundBox.YMin,ymin)
        ymax = max(m.BoundBox.YMax,ymax)
        zmin = min(m.BoundBox.ZMin,zmin)
        zmax = max(m.BoundBox.ZMax,zmax)
     
      return FreeCAD.BoundBox(FreeCAD.Vector(xmin-d,ymin-d,zmin-d),FreeCAD.Vector(xmax+d,ymax+d,zmax+d))


def printWarningSolids(warnSolids,warnEnclosures):

    if warnSolids or warnEnclosures :
       fic = open('Warning_Solids_definition.txt','w')
    else:
       return

    if warnSolids :
       lines = 'Solids :\n'
       for sol in warnSolids:
          lines += '\n'
          lines += '{}\n'.format(sol.__id__)
          lines += '{}\n'.format(sol.Comments)
          lines += '{}\n'.format(writeMCNPCellDef(sol.Definition))
       fic.write(lines)

    if warnEnclosures :
       lines = 'Enclosures :\n'
       for sol in warnEnclosures:
          lines += '\n'
          lines += '{}\n'.format(sol.__id__)
          lines += '{}\n'.format(sol.Comments)
          lines += '{}\n'.format(writeMCNPCellDef(sol.Definition))

       fic.write(lines)

    fic.close()


def joinMetaLists(MList):

   newMetaList = MList[0]
   if MList[0]:
      for M in MList[1:] :
         lastID = newMetaList[-1].__id__+1
         for i,meta in enumerate(M) :
            meta.__id__ = lastID + i
            newMetaList.append(meta)

   return newMetaList     

def excludeCells(MetaList,labelList):
    voidMeta = []
    for m in MetaList:
        if m.IsEnclosure : continue
        found = False
        for label in labelList:
           if label in m.Comments :
              found = True
              break
        if not found :
           voidMeta.append(m)

    return voidMeta       



def sortEnclosure(MetaList,MetaVoid,offSet=0):

    newList = {}
    for m in MetaVoid:
       if m.EnclosureID in newList.keys():
          newList[m.EnclosureID].append(m)
       else:
          newList[m.EnclosureID] = [m]
    
    icount = offSet
    newMeta = []
    for m in MetaList:
      if m.NullCell : continue
      if m.IsEnclosure:
         lineComment = """\
##########################################################
             ENCLOSURE {}
##########################################################""".format(m.EnclosureID)
         mc = UF.GEOUNED_Solid(None)
         mc.Comments = lineComment
         newMeta.append(mc)
         for e in newList[m.EnclosureID] :
            if e.NullCell : continue
            icount += 1
            e.__id__ = icount
            newMeta.append(e)
         lineComment = """\
##########################################################
            END  ENCLOSURE {}
##########################################################""".format(m.EnclosureID)
         mc = UF.GEOUNED_Solid(None)
         mc.Comments = lineComment
         newMeta.append(mc)

      else:
         icount += 1
         m.__id__ = icount
         newMeta.append(m)


    lineComment = """\
##########################################################
             VOIDS 
##########################################################"""
    mc = UF.GEOUNED_Solid(None)
    mc.Comments = lineComment
    newMeta.append(mc)

    for v in newList[0] :
        if v.NullCell : continue
        icount += 1
        v.__id__ = icount
        newMeta.append(v)
    
    return newMeta

def sortEnclosure_old(MetaList):
    newList = {}
    enclID = []
    for m in MetaList:
      if m.ParentEnclosureID not in enclID:
         enclID.append(m.ParentEnclosureID)
         newList[m.ParentEnclosureID]=[m]
      else:
         newList[m.ParentEnclosureID].append(m)
    
    if -2 in enclID:
       enclID.remove(-2)
       graveYard = True
    else:
       graveYard = False

    icount = 0
    newMeta = []
    for eID in enclID:
      if eID != -1 :
         lineComment = """\
##########################################################
             ENCLOSURE {}
##########################################################""".format(eID)
         mc = UF.GEOUNED_Solid(None)
         mc.Comments = lineComment
         newMeta.append(mc)

      for m in newList[eID] :
         icount += 1
         m.__id__ = icount
         newMeta.append(m)

    if graveYard:
      for m in newList[-2] :
         icount += 1
         m.__id__ = icount
         newMeta.append(m)
    
    return newMeta






src/GEOUNED/__init__.py
GEOUNED-code/GEOUNED
import sys
import os
import subprocess

sys.path.append('/opt/geouned/v0.9.8.2/')
sys.path.append('/usr/lib64/freecad/lib64/')

from GEOUNED import GEOUNED

def setInput(inName,inpDir,outDir):

   if inName.endswith('.step') :
     filename = inName[0:-5]
   elif inName.endswith('.stp') :
     filename = inName[0:-4]
   else :
     filename = inName

   if inpDir == '':
    inpDir = '.'
   if outDir == '':
    outDir = '.'

   inName  = '{}/{}'.format(inpDir,inName)
   outName = '{}/{}.i'.format(outDir,filename)

   template='''[Files]
title    = Input Test
stepFile = {}
mcnpFile = {}

[Parameters]
compSolids = False
volCARD    = False
volSDEF    = True
voidGen    = True
dummyMat    = True
minVoidSize =  100
cellSummaryFile = False
cellCommentFile = False
debug       = False
simplify   = No

[Options]
forceCylinder = False
splitTolerance = 0
newSplitPlane = True
nPlaneReverse = 0
'''.format(inName,outName)

   file = open('config.ini','w')
   file.write(template)
   file.close()


def getInputList(folder,ext=None):
    filenameList = []
    if ext is None:
       return os.listdir(folder)

    elif type(ext) is str:
       if ext[0] != '.' : ext = '.' + ext
       for f in os.listdir(folder) :
          if f.endswith(ext) :
            filenameList.append(f)

    elif type(ext) is list or type(ext) is tuple :
       filenameList = []
       extList = []
       for e in ext:
          if e[0] != '.' : e = '.' + e
          extList.append(e)
 
       for f in os.listdir(folder) :
          for e in extList:
             if f.endswith(e) :
                filenameList.append(f)

    return filenameList


def runMCNP(path,inpFile):
   code = '/opt/mcnp6/last/bin/mcnp6'
   xsdir = '/opt/mcnp.data/ascii/xsdir'
   inp = inpFile
   out = inpFile[0:-1]+'o'
   mctal = inpFile[0:-1]+'m'
   cmd = 'cd {} && {} i={} o={} mctal={} xsdir={}'.format(path,code,inp,out,mctal,xsdir)
   os.system(cmd)

def clean(folder):
   filename = '{}/runtpe'.format(folder) 
   os.remove(filename)

def cleanDir(folder):
   for file in getInputList(folder,('.o','.m')):
      filename = '{}/{}'.format(folder,file) 
      os.remove(filename)

def checkLost(outp):
    cmd = 'grep \'particles got lost.\' {}'.format(outp)
    stdout = subprocess.getoutput(cmd)
    lineout = stdout.split('\n')
    lost = 0
    for l in lineout :
       if l=='' or 'terminated' in l  : continue
       lost = int( l[0:l.index('particles')])
    return lost 
    

def getMctalValues(mctal):
   file = open(mctal,'r')
   while True:
      line = file.readline() 
      if 'vals' in line: break

   rawData=[]
   line = file.readline() 
   while 'tfc' not in line:
     rawData.extend(line.split())
     line = file.readline()
   file.close()

   values=[]
   for i in range(int(len(rawData)/2)) :
      val=float(rawData[2*i]  )
      err=float(rawData[2*i+1])
      values.append((val,err))
   return values 
   
def printResults(f,res,lost):
   
    line='{} :\n'.format(f)
    if lost != 0: line +=  '{} particles got lost.\n'.format(lost)

    for i,vals in enumerate(res):
      line+='  {:<3d} : {:8.5f}  {:7.5f}\n'.format(i+1,vals[0],vals[1])
    print(line)


def postProcess(folder):
   for fmct in getInputList(folder,'.m'):
      fout = fmct[:-1]+'o'
      mctal = '{}/{}'.format(folder,fmct)
      outp = '{}/{}'.format(folder,fout)
      res = getMctalValues(mctal)
      lost = checkLost(outp)
      printResults(fmct[:-1]+'i',res,lost)

# ****************************************************
inpDir = 'inputSTEP/Misc'
outDir = 'outMCNP/Misc'
inifile = 'config.ini'


for f in getInputList(inpDir,('stp','step')):
   setInput(f,inpDir,outDir)
   GEO = GEOUNED(inifile)
   GEO.SetOptions()
   GEO.Start()
   del GEO
   
cleanDir(outDir)
for f in getInputList(outDir,'.i'):
   runMCNP(outDir,f)
   clean(outDir)

postProcess(outDir)

testing/test.py
GEOUNED-code/GEOUNED