Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:48:45

0001 #!/usr/bin/env python
0002 #
0003 # Copyright (c) 2019 Opticks Team. All Rights Reserved.
0004 #
0005 # This file is part of Opticks
0006 # (see https://bitbucket.org/simoncblyth/opticks).
0007 #
0008 # Licensed under the Apache License, Version 2.0 (the "License"); 
0009 # you may not use this file except in compliance with the License.  
0010 # You may obtain a copy of the License at
0011 #
0012 #   http://www.apache.org/licenses/LICENSE-2.0
0013 #
0014 # Unless required by applicable law or agreed to in writing, software 
0015 # distributed under the License is distributed on an "AS IS" BASIS, 
0016 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
0017 # See the License for the specific language governing permissions and 
0018 # limitations under the License.
0019 #
0020 
0021 """
0022 ddbase.py : Detdesc parsing into wrapped Elem tree
0023 ====================================================
0024 
0025 Questions 
0026 -----------
0027 
0028 Fly in ointment is that there is detdesc xml generation
0029 which means cannot understand whats going on from sources
0030 alone.
0031 
0032 ABANDONED : Detdesc Cross File Referencing
0033 ----------------------------------------------
0034 
0035 Catalog declares things that can be referenced from other files and
0036 defines the references for them eg /dd/Geometry
0037 
0038 DDDB/geometry.xml::
0039 
0040      04 <DDDB>
0041       5 
0042       6   <catalog name="Geometry">
0043       7 
0044      ..
0045      30 
0046      31     <catalogref href="PMT/geometry.xml#PMT" />
0047      ..
0048      41   </catalog>
0049      42 
0050      43 </DDDB>
0051 
0052 
0053 DDDB/PMT/geometry.xml::
0054 
0055      06 <DDDB>
0056       7 
0057       8   <catalog name="PMT">
0058       9 
0059      10     <logvolref href="hemi-pmt.xml#lvPmtHemiFrame"/>
0060      11     <logvolref href="hemi-pmt.xml#lvPmtHemi"/>
0061      12     <logvolref href="hemi-pmt.xml#lvPmtHemiwPmtHolder"/>
0062      13     <logvolref href="hemi-pmt.xml#lvAdPmtCollar"/>
0063 
0064 
0065 ::
0066 
0067     simon:DDDB blyth$ pmt-dfind /dd/Geometry/PMT/lvPmtHemi\"
0068     ./PMT/hemi-pmt.xml:     <physvol name="pvPmtHemi" logvol="/dd/Geometry/PMT/lvPmtHemi" />
0069     ./PMT/hemi-pmt.xml:     <physvol name="pvAdPmt" logvol="/dd/Geometry/PMT/lvPmtHemi" />
0070     ./PMT/pmt.xml:       volsecond="/dd/Geometry/PMT/lvPmtHemi">
0071     ./PmtBox/geometry.xml:    <physvol name="pvPmtBoxPmt" logvol="/dd/Geometry/PMT/lvPmtHemi">
0072     ./PmtPanel/properties.xml:     volfirst="/dd/Geometry/PMT/lvPmtHemi"
0073     ./PmtPanel/properties.xml:     volfirst="/dd/Geometry/PMT/lvPmtHemi"
0074     simon:DDDB blyth$ 
0075 
0076     simon:DDDB blyth$ pmt-dfind /dd/Geometry/PMT/lvPmtHemiFrame\"
0077     ./PmtPanel/vetopmt.xml:    <physvol name="pvVetoPmtUnit" logvol="/dd/Geometry/PMT/lvPmtHemiFrame">
0078     simon:DDDB blyth$ 
0079 
0080     simon:DDDB blyth$ pmt-dfind /dd/Geometry/PMT/lvPmtHemiwPmtHolder\"
0081     ./AdPmts/geometry.xml:  <physvol name="pvAdPmtUnit" logvol="/dd/Geometry/PMT/lvPmtHemiwPmtHolder">
0082     simon:DDDB blyth$ 
0083 
0084 
0085 
0086 
0087 * Relies on all paramters being defined within the file
0088   (XML entity inclusion is regarded as being withn the same file)
0089 
0090 
0091 Classes
0092 --------
0093 
0094 Att(object) 
0095     holds string expression "expr" and top level global "g" 
0096     allowing the expression to be evaluated within the global context  
0097     via the "value" property 
0098  
0099     For simple properties this is not needed,  only need for expression 
0100     properties.
0101 
0102 
0103 Elem(object)
0104     ctor simply holds raw lxml elem and global "g", 
0105 
0106     Primary functionality invoked via findall_ which takes the 
0107     lxml elements returned by lxml findall and wraps them into 
0108     Elem subclasses appropriate to the lxml tag
0109      
0110     Also provides introspection. 
0111 
0112 
0113 Dddb(Elem)
0114     top level global g that holds the context within which txt expr 
0115     are evaluated via Att "value" properties 
0116 
0117 Parameter(Elem)
0118     prop: expr
0119 
0120 PosXYZ(Elem)
0121     att: x,y,z 
0122 
0123     att rather than simple props are needed as they are expressions 
0124     that must be evaluated within the global context 
0125 
0126 Primitive(Elem)
0127     att: outerRadius, innerRadius
0128 
0129 Sphere(Primitive)
0130     att: startThetaAngle, deltaThetaAngle
0131 
0132 Tubs(Primitive)
0133     att: sizeZ
0134 
0135 Logvol(Elem)
0136     prop: material, sensdet 
0137 
0138 Physvol(Elem)
0139     prop: logvolref
0140 
0141 Union(Elem)
0142 Intersection(Elem)
0143 Subtraction(Elem)
0144     naming types
0145 
0146 
0147 
0148 
0149 """
0150 import os, re, logging, math
0151 log = logging.getLogger(__name__)
0152 
0153 from opticks.ana.main import opticks_main
0154 
0155 import numpy as np
0156 import lxml.etree as ET
0157 import lxml.html as HT
0158 from math import acos   # needed for detdesc string evaluation during context building
0159 
0160 tostring_ = lambda _:ET.tostring(_)
0161 exists_ = lambda _:os.path.exists(os.path.expandvars(_))
0162 parse_ = lambda _:ET.parse(os.path.expandvars(_)).getroot()
0163 fparse_ = lambda _:HT.fragments_fromstring(file(os.path.expandvars(_)).read())
0164 pp_ = lambda d:"\n".join([" %30s : %f " % (k,d[k]) for k in sorted(d.keys())])
0165 
0166 X,Y,Z = 0,1,2
0167 
0168 
0169 class Att(object):
0170     def __init__(self, expr, g, evaluate=True):
0171         self.expr = expr
0172         self.g = g  
0173         self.evaluate = evaluate
0174 
0175     value = property(lambda self:self.g.ctx.evaluate(self.expr) if self.evaluate else self.expr)
0176 
0177     def __repr__(self):
0178         return "%s : %s " % (self.expr, self.value)
0179 
0180 
0181 class E(object):
0182     """
0183     Element base type, providing lxml elem lookup and wrapping 
0184     """
0185     lvtype = 'Logvol'
0186     pvtype = 'Physvol'
0187     postype = 'posXYZ'
0188 
0189     typ = property(lambda self:self.__class__.__name__)
0190     name  = property(lambda self:self.elem.attrib.get('name',None))
0191     shortname = property(lambda self:self.name)   # for correspondence with GDML branch 
0192 
0193     def __init__(self, elem, g=None):
0194         """
0195         :param elem: lxml parsed DetDesc XML element
0196         :param g: registry object 
0197 
0198         The registry object (Dddb class) is planted at top level 
0199         and passed along to all E instances.
0200         """
0201         self.elem = elem 
0202         self.g = g 
0203 
0204     def att(self, k, dflt=None):
0205         v = self.elem.attrib.get(k, None)
0206         return Att(v, self.g) if v is not None else Att(dflt, self.g, evaluate=False) 
0207 
0208     def findall_(self, expr):
0209         """
0210         lxml findall result elements are wrapped in the class appropriate to their tags,
0211         note the global g gets passed into all Elem
0212 
0213         g.kls is a dict associating tag names to classes, Elem 
0214         is fallback, all the classes have signature of  elem-instance, g 
0215 
0216         """
0217         wrap_ = lambda e:self.g.kls.get(e.tag,E)(e,self.g)
0218         fa = map(wrap_, self.elem.findall(expr) )
0219         kln = self.__class__.__name__
0220         name = self.name 
0221         log.debug("findall_ from %s:%s expr:%s returned %s " % (kln, name, expr, len(fa)))
0222         return fa 
0223 
0224     def findone_(self, expr):
0225         all_ = self.findall_(expr)
0226         assert len(all_) == 1
0227         return all_[0]
0228 
0229     def find1_(self, expr):
0230         all_ = self.findall_(expr)
0231         assert len(all_) in [0,1]
0232         return all_[0] if len(all_) == 1 else None
0233 
0234     def find_(self, expr):
0235         e = self.elem.find(expr) 
0236         wrap_ = lambda e:self.g.kls.get(e.tag,Elem)(e,self.g)
0237         return wrap_(e) if e is not None else None
0238 
0239     def __repr__(self):
0240         return "%15s : %s " % ( self.elem.tag, repr(self.elem.attrib) )
0241 
0242 
0243 
0244 class Elem(E):
0245     posXYZ = None
0246     is_rev = False
0247 
0248     # structure avoids having to forward declare classes
0249     is_primitive = property(lambda self:type(self) in self.g.primitive)
0250     is_composite = property(lambda self:type(self) in self.g.composite)
0251     is_intersection = property(lambda self:type(self) in self.g.intersection)
0252     is_difference = property(lambda self:type(self) in self.g.difference)
0253     is_operator = property(lambda self:type(self) in self.g.operator)
0254     is_tubs = property(lambda self:type(self) in self.g.tubs)
0255     is_sphere = property(lambda self:type(self) in self.g.sphere)
0256     is_union = property(lambda self:type(self) in self.g.union)
0257     is_posXYZ = property(lambda self:type(self) in self.g.posXYZ)
0258     is_geometry  = property(lambda self:type(self) in self.g.geometry)
0259     is_logvol  = property(lambda self:type(self) in self.g.logvol)
0260 
0261     @classmethod
0262     def link_posXYZ(cls, ls, posXYZ):
0263         """
0264         Attach *posXYZ* attribute to all primitives in the list 
0265         """
0266         for i in range(len(ls)):
0267             if ls[i].is_primitive:
0268                 ls[i].posXYZ = posXYZ 
0269                 log.debug("linking %s to %s " % (posXYZ, ls[i]))
0270 
0271     @classmethod
0272     def combine_posXYZ(cls, prim, plus):
0273         assert 0, "not yet implemented"
0274  
0275     @classmethod
0276     def link_prior_posXYZ(cls, ls, base=None):
0277         """
0278         Attach any *posXYZ* instances in the list to preceeding primitives
0279 
0280         Singular base linking is needed to honour posXYZ applied on a pv 
0281         which gets hooked onto the referenced lv and where the lv yields a primitive.
0282 
0283        ::
0284 
0285             105 
0286             106     <physvol name="pvPmtHemiBottom"
0287             107          logvol="/dd/Geometry/PMT/lvPmtHemiBottom">
0288             108       <posXYZ z="PmtHemiFaceOff+PmtHemiBellyOff"/>
0289             109     </physvol>
0290             110 
0291             111     <physvol name="pvPmtHemiDynode"
0292             112          logvol="/dd/Geometry/PMT/lvPmtHemiDynode">
0293             113       <posXYZ z="-0.5*PmtHemiGlassBaseLength+PmtHemiGlassThickness"/>
0294             114     </physvol>
0295 
0296  
0297         """
0298         log.info("link_prior_posXYZ lls %d base %r " % (len(ls), base))
0299         if len(ls) > 1:
0300             for i in range(1,len(ls)):   # start from 1 to avoid list wraparound ?
0301                 if base is not None and ls[i-1].is_primitive:
0302                     ls[i-1].posXYZ = base 
0303                 pass
0304                 if ls[i].is_posXYZ and ls[i-1].is_primitive:
0305                     if ls[i-1].posXYZ is not None:
0306                         cls.combine_posXYZ(ls[i-1], ls[i])
0307                     else:
0308                         ls[i-1].posXYZ = ls[i] 
0309                     log.debug("linking %s to %s " % (ls[i], ls[i-1]))
0310                 pass
0311             pass
0312         elif len(ls) == 1:
0313             if base is not None and ls[0].is_primitive:
0314                 log.info(" link_prior_posXYZ doing singular base link " ) 
0315                 ls[0].posXYZ = base
0316             pass
0317         else:
0318             pass
0319             
0320 
0321     def _get_desc(self):
0322         return "%10s %15s %s " % (type(self).__name__, self.xyz, self.name )
0323     desc = property(_get_desc)
0324 
0325     def _get_xyz(self):
0326        x = y = z = 0
0327        if self.posXYZ is not None:
0328            x = self.posXYZ.x.value 
0329            y = self.posXYZ.y.value 
0330            z = self.posXYZ.z.value 
0331        pass
0332        return [x,y,z] 
0333     xyz = property(_get_xyz)
0334 
0335     def _get_z(self):
0336        """
0337        z value from any linked *posXYZ* or 0 
0338        """
0339        z = 0
0340        if self.posXYZ is not None:
0341            z = self.posXYZ.z.value 
0342        return z
0343     z = property(_get_z)
0344 
0345     def _get_children(self):
0346         """
0347         Defines the nature of the tree. 
0348 
0349         * for Physvol returns single item list containing the referenced Logvol
0350         * for Logvol returns list of all contained Physvol
0351         * otherwise returns empty list 
0352 
0353         NB bits of geometry of a Logvol are not regarded as children, 
0354         but rather are constitutent to it.
0355 
0356         Fixed Issue of physvol offsets not being honoured::
0357 
0358             105 
0359             106     <physvol name="pvPmtHemiBottom"
0360             107          logvol="/dd/Geometry/PMT/lvPmtHemiBottom">
0361             108       <posXYZ z="PmtHemiFaceOff+PmtHemiBellyOff"/>
0362             109     </physvol>
0363             110 
0364             111     <physvol name="pvPmtHemiDynode"
0365             112          logvol="/dd/Geometry/PMT/lvPmtHemiDynode">
0366             113       <posXYZ z="-0.5*PmtHemiGlassBaseLength+PmtHemiGlassThickness"/>
0367             114     </physvol>
0368 
0369         """
0370         if type(self) is Physvol:
0371             posXYZ = self.find_("./posXYZ")
0372             lvn = self.logvolref.split("/")[-1]
0373             lv = self.g.logvol_(lvn)
0374 
0375             assert getattr(lv,'posXYZ',None) == None,  ("_get_children stomping on posXYZ ", lv)
0376             lv.posXYZ = posXYZ   
0377     
0378             ## Hmm: Associating a posXYZ to an lv should probably be done 
0379             ##      on a clone of the lv ?  As there may be multiple such placements
0380             ##      and dont want prior placements to get stomped on.
0381             ##
0382             ##      But as checked above the stomping doesnt happen.
0383             ##
0384             ##      Leaving ASIS because detdesc parsing is a deadend 
0385             ##      that was only ever applied to simple PMT, and have no
0386             ##      plans to develop it further, GDML parsing being much 
0387             ##      more useful.
0388             ##
0389             ## 2017-10-21 
0390             ##     why not just attach to self (the pv) ? see treebase.py:Node.create
0391             ##     Because nexus of control of partitioning operates on LV, not PV 
0392             ## 
0393 
0394             if posXYZ is not None:
0395                 log.info("children... posXYZ %s  " % (repr(posXYZ))) 
0396                 log.info("children... %s passing pv posXYZ to lv %s  " % (self.name, repr(lv))) 
0397             pass
0398 
0399             return [lv]
0400 
0401         elif type(self) is Logvol:
0402             pvs = self.findall_("./physvol")
0403             return pvs
0404         else:
0405             return []  
0406         pass
0407 
0408     children = property(_get_children)
0409 
0410     def comps(self):
0411         """
0412         :return comps: immediate constituents of an Elem, not recursive
0413         """
0414         comps = self.findall_("./*")
0415         self.link_prior_posXYZ(comps)
0416         return comps
0417 
0418     def geometry(self):
0419         return filter(lambda c:c.is_geometry, self.comps())
0420 
0421 
0422 class Logvol(Elem):
0423     material = property(lambda self:self.elem.attrib.get('material', None))
0424     sensdet = property(lambda self:self.elem.attrib.get('sensdet', None))
0425     def __repr__(self):
0426         return "LV %-20s %20s %s : %s " % (self.name, self.material, self.sensdet, repr(self.posXYZ))
0427 
0428 class Physvol(Elem):
0429     logvolref = property(lambda self:self.elem.attrib.get('logvol', None))
0430     def __repr__(self):
0431         return "PV %-20s %s " % (self.name, self.logvolref)
0432 
0433 class Union(Elem):
0434     def __repr__(self):
0435         return "Union %20s  " % (self.name)
0436 
0437 class Intersection(Elem):
0438     def __repr__(self):
0439         return "Intersection %20s  " % (self.name)
0440 
0441 class Subtraction(Elem):
0442     def __repr__(self):
0443         return "Subtraction %20s  " % (self.name)
0444 
0445 
0446 
0447 class Parameter(Elem):
0448     expr = property(lambda self:self.elem.attrib['value'])
0449 
0450     def hasprefix(self, prefix):
0451         return self.name.startswith(prefix)
0452 
0453     def __repr__(self):
0454         return "%30s : %s " % ( self.name, self.expr )
0455 
0456 
0457 class PosXYZ(Elem):
0458     x = property(lambda self:self.att('x',0))
0459     y = property(lambda self:self.att('y',0))
0460     z = property(lambda self:self.att('z',0))
0461     def __repr__(self):
0462         return "PosXYZ  %s  " % (repr(self.z))
0463 
0464 
0465 class Primitive(Elem):
0466     is_rev = True
0467     outerRadius = property(lambda self:self.att('outerRadius'))
0468     innerRadius = property(lambda self:self.att('innerRadius'))
0469 
0470     def bbox(self, zl, zr, yn, yp ):
0471         assert yn < 0 and yp > 0 and zr > zl
0472         return BBox([yn,yn,zl], [yp,yp,zr])
0473  
0474 
0475 class Sphere(Primitive):
0476     startThetaAngle = property(lambda self:self.att('startThetaAngle'))
0477     deltaThetaAngle = property(lambda self:self.att('deltaThetaAngle'))
0478 
0479     def has_inner(self):
0480         return self.innerRadius.value is not None 
0481 
0482     def has_innerRadius(self):
0483         return self.innerRadius.value is not None 
0484 
0485     def has_startThetaAngle(self):
0486         return self.startThetaAngle.value is not None 
0487 
0488     def has_deltaThetaAngle(self):
0489         return self.deltaThetaAngle.value is not None 
0490 
0491     def __repr__(self):
0492         return "sphere %20s : %s :  %s " % (self.name, self.outerRadius, self.posXYZ)
0493 
0494 class Tubs(Primitive):
0495     sizeZ = property(lambda self:self.att('sizeZ'))
0496     def __repr__(self):
0497         return "Tubs %20s : outerRadius %s  sizeZ %s  :  %s " % (self.name, self.outerRadius, self.sizeZ, self.posXYZ)
0498 
0499 
0500 
0501 
0502 class Dddb(Elem):
0503     kls = {
0504         "parameter":Parameter,
0505         "sphere":Sphere,
0506         "tubs":Tubs,
0507         "logvol":Logvol,
0508         "physvol":Physvol,
0509         "posXYZ":PosXYZ,
0510         "intersection":Intersection,
0511         "union":Union,
0512         "subtraction":Subtraction,    # not seen in wild
0513     }
0514 
0515 
0516     primitive = [Sphere, Tubs]
0517     tubs = [Tubs]
0518     sphere = [Sphere]
0519 
0520     intersection = [Intersection]
0521     union = [Union]
0522     subtraction = [Subtraction]
0523     operator = [Union, Intersection, Subtraction]
0524 
0525     composite = [Union, Intersection, Subtraction, Physvol]  # who uses this ? funny combo
0526 
0527     geometry = [Sphere, Tubs, Union, Intersection, Subtraction]
0528 
0529     posXYZ= [PosXYZ]
0530     logvol = [Logvol]
0531 
0532     expand = {
0533         "(PmtHemiFaceROCvac^2-PmtHemiBellyROCvac^2-(PmtHemiFaceOff-PmtHemiBellyOff)^2)/(2*(PmtHemiFaceOff-PmtHemiBellyOff))":
0534          "(PmtHemiFaceROCvac*PmtHemiFaceROCvac-PmtHemiBellyROCvac*PmtHemiBellyROCvac-(PmtHemiFaceOff-PmtHemiBellyOff)*(PmtHemiFaceOff-PmtHemiBellyOff))/(2*(PmtHemiFaceOff-PmtHemiBellyOff))" 
0535 
0536     }
0537 
0538     @classmethod
0539     def parse(cls, path, analytic_version=0):
0540         log.info("Dddb parsing %s " % path )
0541         g = Dddb(parse_(path))
0542         g.init(analytic_version=analytic_version)
0543         return g
0544 
0545     def __call__(self, expr):
0546         return self.ctx.evaluate(expr)
0547 
0548     def init(self, analytic_version):
0549         self.g = self
0550         self.analytic_version = analytic_version
0551 
0552         pctx = {}
0553         pctx["mm"] = 1.0 
0554         pctx["cm"] = 10.0 
0555         pctx["m"] = 1000.0 
0556         pctx["degree"] = 1.0
0557         pctx["radian"] = 180./math.pi
0558 
0559         self.ctx = Context(pctx, self.expand)
0560         self.ctx.build_context(self.params_())
0561 
0562     def logvol_(self, name):
0563         """
0564         Hmm this referencing is single file only, 
0565         need to build registry of logvol from all files
0566         """
0567         log.info("logvol_ %s " % name)
0568 
0569         if name[0] == '/':
0570             name = name.split("/")[-1]
0571         return self.find_(".//logvol[@name='%s']"%name)
0572 
0573     def logvols_(self):
0574         return self.findall_(".//logvol")
0575 
0576     def params_(self, prefix=None):
0577         pp = self.findall_(".//parameter")
0578         if prefix is not None:
0579             pp = filter(lambda p:p.hasprefix(prefix), pp)
0580         return pp  
0581 
0582     def context_(self, prefix=None):
0583         dd = {}
0584         for k,v in self.ctx.d.items():
0585             if k.startswith(prefix) or prefix is None:
0586                 dd[k] = v
0587         return dd   
0588 
0589     def dump_context(self, prefix=None):
0590         print(pp_(self.context_(prefix)))
0591 
0592 
0593 
0594 
0595 
0596 class Context(object):
0597     def __init__(self, d, expand):
0598         """
0599         :param d: context dict pre-populated with units
0600         :param expand: manual expansion dict, workaround for detdesc 
0601                        expressions that are not valid python 
0602         """
0603         self.d = d
0604         self.expand = expand
0605 
0606     def build_context(self, params):
0607         """
0608         :param params: XML parameter elements  
0609 
0610         Three wave context building avoids having to work out dependencies, 
0611         just repeat and rely on simpler expressions getting set into 
0612         context on prior waves.
0613         """
0614         name_error = params
0615         type_error = []
0616         for wave in range(3):
0617             name_error, type_error = self._build_context(name_error, wave)
0618             log.debug("after wave %s remaining name_error %s type_error %s " % (wave, len(name_error), len(type_error)))
0619         pass
0620         assert len(name_error) == 0
0621         assert len(type_error) == 0
0622 
0623     def evaluate(self, expr):
0624         txt = "float(%s)" % expr
0625         try:
0626             val = eval(txt, globals(), self.d)
0627         except NameError:
0628             log.fatal("failed to evaluate expr %s " % (expr))
0629             val = None 
0630         pass 
0631         return val    
0632 
0633     def _build_context(self, params, wave):
0634         """
0635         :param params: to be evaluated into the context
0636         """
0637         name_error = []
0638         type_error = []
0639         for p in params:
0640             if p.expr in self.expand:
0641                 expr = self.expand[p.expr]
0642                 log.debug("using manual expansion of %s to %s " % (p.expr, expr))
0643             else:
0644                 expr = p.expr
0645 
0646             txt = "float(%s)" % expr
0647             try:
0648                 val = eval(txt, globals(), self.d)
0649                 pass
0650                 self.d[p.name] = float(val)  
0651             except NameError:
0652                 name_error.append(p)
0653                 log.debug("NameError %s %s " % (p.name, txt ))
0654             except TypeError:
0655                 type_error.append(p)
0656                 log.debug("TypeError %s %s " % (p.name, txt ))
0657             pass
0658         return name_error, type_error
0659           
0660     def dump_context(self, prefix):
0661         log.info("dump_context %s* " % prefix ) 
0662         return "\n".join(["%25s : %s " % (k,v) for k,v in filter(lambda kv:kv[0].startswith(prefix),self.d.items())])
0663  
0664     def __repr__(self):
0665         return "\n".join(["%25s : %s " % (k,v) for k,v in self.d.items()])
0666 
0667 
0668 
0669 
0670 class Ref(E):
0671     href = property(lambda self:self.elem.attrib['href'])
0672 
0673     xmlname = property(lambda self:self.href.split("#")[0])
0674     anchor = property(lambda self:self.href.split("#")[1])
0675 
0676     xmlpath = property(lambda self:os.path.join(self.parent.xmldir, self.xmlname))
0677     ddpath = property(lambda self:os.path.join(self.parent.ddpath, self.anchor))
0678 
0679     def __repr__(self):
0680         return "%20s %20s %s %s " % (self.typ, self.anchor, self.xmlpath, self.ddpath )
0681 
0682 class CatalogRef(Ref):
0683     pass
0684 
0685 class LogvolRef(Ref):
0686     pass
0687 
0688 
0689 class Catalog(E):
0690     """
0691     Role of catalog is to provide easy access to 
0692     logvol via simple path access... so this 
0693     needs to maintain the connection between 
0694     
0695     * filesystem paths
0696     * dd paths
0697     * in memory elements 
0698 
0699     """ 
0700     name = property(lambda self:self.elem.attrib['name'])
0701     parent = None
0702 
0703     def _get_ddpath(self):
0704         lev = [""]
0705         if self.parent is not None:
0706             lev.append( self.parent.ddpath )
0707         pass
0708         lev.append(self.name) 
0709         return "/".join(lev)
0710 
0711     ddpath = property(_get_ddpath)
0712 
0713 
0714     def __repr__(self):
0715         return "%20s %20s %s " % (self.typ, self.name, self.ddpath)
0716 
0717     def refs(self):
0718         """
0719         Catalogs often contain catalogref, logvolref but they
0720         can also directly contain logvol.
0721     
0722         Hmm splitting into separate files is an 
0723         implementation detail, should not impact the 
0724         tree being built.
0725         """
0726         refs = self.findall_("./catalogref")
0727         for ref in refs:
0728             ref.parent = self
0729             if not exists_(ref.xmlpath):
0730                 log.warning("ref.xmlpath doesnt exist %s " % ref.xmlpath)
0731         pass
0732         return refs 
0733 
0734 
0735 
0736 class DD(E):
0737     """
0738     CAUTION
0739         This was an initial foray into parsing 
0740         multi-file detdesc that was abandoned.  
0741         Jumped ship to single file GDML parsing, that turned out to be much easier.
0742 
0743     """
0744     ddr = {}
0745 
0746     kls = {
0747         "catalog":Catalog,
0748         "catalogref":CatalogRef,
0749         "logvolref":LogvolRef,
0750     }
0751 
0752     xmldir = property(lambda self:os.path.dirname(self.xmlpath))
0753 
0754     @classmethod
0755     def parse(cls, path):
0756         log.info("DD parsing %s " % path )
0757         g = cls 
0758         dd = DD(parse_(path), g)
0759         dd.xmlpath = path
0760         dd.init()
0761         return dd
0762 
0763     def init(self):
0764         cat = self.find1_("./catalog")
0765         if cat is not None:
0766             print(cat)  
0767             cat.xmldir = self.xmldir
0768             for ref in cat.refs():
0769                 print("%s %s " % (ref, ref.ddpath))
0770                 self.g.ddr[ref.ddpath] = DD.parse(ref.xmlpath) 
0771 
0772     def __repr__(self):
0773         return "%20s %s %s " % (self.typ, self.xmlpath, self.xmldir)
0774 
0775 
0776 
0777 
0778 
0779 
0780 
0781 
0782 
0783 if __name__ == '__main__':
0784     args = opticks_main(apmtidx=2)
0785 
0786 
0787 
0788     xmlpath = args.apmtddpath 
0789     log.info("parsing %s -> %s " % (xmlpath, os.path.expandvars(xmlpath)))
0790 
0791     g = Dddb.parse(xmlpath)
0792 
0793     lv = g.logvol_("lvPmtHemi")
0794 
0795 
0796     dd = DD.parse(args.addpath)
0797 
0798 
0799     print(dd)
0800 
0801 
0802