So, I managed to upgrade to the latest stable version, 1.10.7 and sadly, the problem persists.
I tried quickly writing up a test-case that should reproduce the issue:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.gui.OnscreenText import OnscreenText
class run_me(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.blockDimensions=Point3(4,4,4)
#generate a movable cube:
self.gotMoveableCube=self.generateStandAloneCube(Point4(1,1,1,1.1),Point3(1,1,1),True,False)
self.gotMoveableCube.setX(self.gotMoveableCube.getX()-(self.blockDimensions.x*2))
#generate a starting cube, whose geometry may be extended:
self.extendableNodePath=self.generateStandAloneCube(Point4(0.0352941, 0.968628, 0.905882,1.0),Point3(1,1,1),True,True)
#add the controls for moving the cube and for adding geometry to the starting cube:
self.accept('w',self.moveCube,['front'])
self.accept('s',self.moveCube,['back'])
self.accept('a',self.moveCube,['left'])
self.accept('d',self.moveCube,['right'])
self.accept('q',self.moveCube,['up'])
self.accept('e',self.moveCube,['down'])
self.accept("o", self.addPositionalRequestWrapper)
self.accept("p", self.deletePositionalRequestWrapper)
#add a traverser and a collisionHandlerQueue:
self.traverser = CollisionTraverser('traverser name')
base.cTrav = self.traverser
self.boxEmptyQueue=CollisionHandlerQueue()
#the event that causes the application to freeze:
self.accept("t", self.addColliderToTraverser)
#the list that will act as a queue, storing any requests to extend the starting face geometry:
self.additionalFacesList=[]
self.deleteFacesList=[]
#set up the additional task-chain:
taskMgr.setupTaskChain('asyncTaskName', numThreads = 1, tickClock = None,
threadPriority = None, frameBudget = None,
frameSync = True, timeslicePriority = None)
#the asyncTask that will monitor the list, acting on any requests it finds in there:
taskMgr.add(self.addDeleteCubesTaskRun, 'addDeleteCubesTaskRunName', taskChain = 'asyncTaskName')
#some instructions on the screen:
textA="[WASDQE]: Move the white cube."
textB="[OP]: Add and Remove geometry to the blue cube."
textC="[T]: Add a collider to a traverser."
textD="[After adding a collider to a traverser, as you continue to \n add and remove geometry from the blue cube, the app freezes.]"
textE="FREEZE BUG?*_*"
textF="A collider was added to the traverser."
motionText = OnscreenText(text=textA,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft, fg=(1, 1, 1, 1), pos=(0.06, -0.1), scale=.05,shadow=(0, 0, 0, 0.5))
geometryManipulationText = OnscreenText(text=textB,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.18), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
traverserAddText = OnscreenText(text=textC,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.26), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
bugInfoText = OnscreenText(text=textD,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.34), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
self.colliderAddedText = OnscreenText(text=textF,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.46), fg=(0.91, 0.31, 0.41, 1), scale=.065,shadow=(0, 0, 0, 0.5))
titleText = OnscreenText(text=textE ,style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),pos=(0.8, -0.95), scale = .07)
self.colliderAddedText.hide()
#move the camera behind a bit:
base.cam.setY(base.cam, -100)
#This adds a collider to a collisionTraverser:
def addColliderToTraverser(self):
if(not render.find("dummyNp")):
gotNp=render.attachNewNode("dummyNp")
pointMin=Point3(self.blockDimensions.y*0.05,self.blockDimensions.y*0.05,self.blockDimensions.z*0.05)
pointMax=Point3(self.blockDimensions.x*0.95,self.blockDimensions.y*0.95,self.blockDimensions.z*0.95)
#x 1:
box=CollisionBox(pointMin,pointMax)
cn=render.attachNewNode(CollisionNode('cnode'))
cn.node().addSolid(box)
cn.setName("x1")
cn.show()
cn.setPos(gotNp.getPos())
cn.setX(cn.getX()+self.blockDimensions.x)
cn.wrtReparentTo(gotNp)
cn.node().setIntoCollideMask(BitMask32.allOff())
base.cTrav.addCollider(cn, self.boxEmptyQueue)
self.colliderAddedText.show()
#This is a task on another thread that adds a request to extend the blue cube's geometry into the queue:
def addRequestsAsyncAdd(self,task):
if(self.counterReqAdd==1):
if(self.gotMoveableCube.getPos() not in self.additionalFacesList):
self.additionalFacesList.append(self.gotMoveableCube.getPos())
self.counterReqAdd=0
taskMgr.remove("addRequestsAsyncAddNom")
else:
self.counterReqAdd+=1
return task.cont
#This is a wrapper that adds a task to another thread dealing with adding requests to extend the blue cube's geometry:
def addPositionalRequestWrapper(self):
self.counterReqAdd=0
taskMgr.add(self.addRequestsAsyncAdd, 'addRequestsAsyncAddNom',taskChain = 'asyncTaskName')
#This is a task on another thread that adds a request to delete the blue cube's geometry into the queue:
def addRequestsAsyncDel(self,task):
if(self.counterReqDel==1):
if(self.gotMoveableCube.getPos() not in self.deleteFacesList):
self.deleteFacesList.append(self.gotMoveableCube.getPos())
self.counterReqDel=0
taskMgr.remove("addRequestsAsyncDelNom")
else:
self.counterReqDel+=1
return task.cont
#This is a wrapper that adds a task to another thread dealing with adding requests to delete the blue cube's geometry:
def deletePositionalRequestWrapper(self):
self.counterReqDel=0
taskMgr.add(self.addRequestsAsyncDel, 'addRequestsAsyncDelNom',taskChain = 'asyncTaskName')
#This simply moves the movable cube around:
def moveCube(self,value):
if(value=="front"):
self.gotMoveableCube.setY(self.gotMoveableCube,self.blockDimensions.y)
if(value=="back"):
self.gotMoveableCube.setY(self.gotMoveableCube,-self.blockDimensions.y)
if(value=="left"):
self.gotMoveableCube.setX(self.gotMoveableCube,-self.blockDimensions.x)
if(value=="right"):
self.gotMoveableCube.setX(self.gotMoveableCube,self.blockDimensions.x)
if(value=="up"):
self.gotMoveableCube.setZ(self.gotMoveableCube,self.blockDimensions.z)
if(value=="down"):
self.gotMoveableCube.setZ(self.gotMoveableCube,-self.blockDimensions.z)
#This is the asyncTask that calls the methods which add or remove geometry from the blue cube:
def addDeleteCubesTaskRun(self,task):
if(len(self.additionalFacesList)>0):
sentPoint=self.additionalFacesList[0]
self.extendExistingNodePath([sentPoint,[]],self.extendableNodePath)
del self.additionalFacesList[0]
elif(len(self.deleteFacesList)>0):
sentPoint=self.deleteFacesList[0]
self.deleteCubePoint(sentPoint,self.extendableNodePath)
del self.deleteFacesList[0]
return task.cont
#This calls the method that removes geometry from each face of the blue cube:
def deleteCubePoint(self,sentPoint,sentNp):
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[1]],sentNp)
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[2]],sentNp)
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[3]],sentNp)
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[4]],sentNp)
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[5]],sentNp)
extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[6]],sentNp)
#This is the actual method that will remove geometry from the blue cube:
def removeFaceFromExistingNodePath(self,pointFaceHybridList,sentNp):
allFaceArrayPoints=[]
for index in range(0,len(pointFaceHybridList),2):
sentPoint=pointFaceHybridList[index]
faceSideList=pointFaceHybridList[index+1]
for faceSide in faceSideList:
gotFaceArrayPoints=self.returnProperFaceCoordinates(sentPoint,self.blockDimensions,faceSide)
allFaceArrayPoints.append(gotFaceArrayPoints)
terrainGeomNode=sentNp.node()
specificGeom=terrainGeomNode.modifyGeom(0)
vdata = specificGeom.modifyVertexData()
vertexReader = GeomVertexReader(vdata, 'vertex')
prim=specificGeom.modifyPrimitive(0)
vertexArrayData=prim.modifyVertices()
vertexArrayHandleP=vertexArrayData.modifyHandle()
vertexArrayFormatP=vertexArrayHandleP.getArrayFormat()
lengthP=vertexArrayFormatP.getStride()
primitiveArrayModder=GeomVertexWriter(vertexArrayData,0)
primitiveArrayReader=GeomVertexReader(vertexArrayData,0)
vertexArray=vdata.modifyArray(0)
vertexArrayHandle=vertexArray.modifyHandle()
vertexArrayFormat=vertexArrayHandle.getArrayFormat()
length=vertexArrayFormat.getStride()
bufferDat=[]
gotIt=prim.getVertexList()
if(len(gotIt)>0):
minVal=min(gotIt)
maxVal=max(gotIt)
totalPointsToRemove=len(allFaceArrayPoints)*4
totalPointsFound=0
for indxx in range(minVal,maxVal,4):
vi=indxx
vii=indxx+1
viii=indxx+2
viv=indxx+3
vertexReader.setRow(vi)
v1 = vertexReader.getData3f()
vertexReader.setRow(vii)
v2 = vertexReader.getData3f()
vertexReader.setRow(viii)
v3 = vertexReader.getData3f()
vertexReader.setRow(viv)
v4 = vertexReader.getData3f()
faceArrayTest=[v1,v2,v3,v4]
for gotFaceArrayPoints in allFaceArrayPoints:
foundPoints=0
for singlePoint in faceArrayTest:
if(singlePoint in gotFaceArrayPoints):
foundPoints+=1
if(foundPoints==4):
bufferDat.extend([indxx,indxx+1,indxx+2,indxx+3])
totalPointsFound+=4
break
if(foundPoints==4):
break
if(totalPointsFound==totalPointsToRemove):
break
totalPointsFound=0
for kid in sentNp.getChildren():
for kid2 in kid.getChildren():
if isinstance(kid2.node(),CollisionNode):
lenzz=kid2.node().getNumSolids()
startNum=0
while startNum<lenzz:
faceArrayTest=[]
collisionSolidToUze=kid2.node().getSolid(startNum)
for indexx in range(4):
pointToAppend=Point3(collisionSolidToUze.getPoint(indexx).x,collisionSolidToUze.getPoint(indexx).y,collisionSolidToUze.getPoint(indexx).z)
faceArrayTest.append(pointToAppend)
for gotFaceArrayPoints in allFaceArrayPoints:
foundPoints=0
for singlePoint in faceArrayTest:
if(singlePoint in gotFaceArrayPoints):
foundPoints+=1
if(foundPoints==4):
#remove the solid:
kid2.node().removeSolid(startNum)
startNum-=1
lenzz=kid2.node().getNumSolids()
totalPointsFound+=4
break
if(foundPoints==4):
break
if(totalPointsFound==totalPointsToRemove):
break
startNum+=1
if(kid2.node().getNumSolids()==0):
kid2.removeNode()
break
#now, remove the relevant vertices from the primitive:
delPrimz=[]
kounter=0
while not primitiveArrayReader.isAtEnd():
rowIndex=primitiveArrayReader.getData1i()
if(rowIndex in bufferDat):
delPrimz.append(kounter)
#print "HNU!",rowIndex
kounter+=1
for start in sorted(delPrimz, reverse=True):
vertexArrayHandleP.setSubdata(start*lengthP, lengthP, b'')
rNumz2=vertexArrayData.getNumRows()
oldRowIndices=list(range(vdata.getNumRows()))
numVertReduce=0
for start in sorted(bufferDat, reverse=True):
vertexArrayHandle.setSubdata(start*length, length, b'')
numVertReduce-=1
del oldRowIndices[start]
if(len(delPrimz)>0):
lenRem=len(delPrimz)
lRNum=rNumz2
beginUpdateRow=min(delPrimz)
startRow=primitiveArrayReader.getStartRow()
primitiveArrayReader.setRow(startRow)
kounter=0
while not primitiveArrayReader.isAtEnd():
rowNow=primitiveArrayReader.getReadRow()-1
oldRowIndex=primitiveArrayReader.getData1i()
if(kounter>=beginUpdateRow):
oldRowIndex+=numVertReduce
primitiveArrayModder.setData1i(oldRowIndex)
kounter+=1
#This is the actual method that adds geometry to the blue cube:
def extendExistingNodePath(self,sentPointHybridBlob,sentNpToExtend):
activeColourMod=Point4(0.0352941, 0.968628, 0.905882,1.0)
gnode=sentNpToExtend.node()
geom=gnode.modifyGeom(0)
vdata = geom.modifyVertexData()
vertexMod = GeomVertexWriter(vdata, 'vertex')
normalMod = GeomVertexWriter(vdata, 'normal')
colorMod = GeomVertexWriter(vdata, 'color')
texcoordMod = GeomVertexWriter(vdata, 'texcoord')
vertex = GeomVertexReader(vdata, 'vertex')
counter=vdata.getNumRows()
primitive=geom.modifyPrimitive(0)
collNodeII=CollisionNode("blockTestCollision")
counter=vdata.getNumRows()
for index in range(0,len(sentPointHybridBlob),2):
sentPoint=sentPointHybridBlob[index]
exemptFaces=sentPointHybridBlob[index+1]
gotArrayIs=self.generateCubeGeneric(sentPoint,self.blockDimensions,exemptFaces)
for currentArray in gotArrayIs:
self.drawAFaceMod(vertexMod,normalMod,colorMod,texcoordMod,primitive,counter,activeColourMod,collNodeII,currentArray)
counter+=4
primitive.closePrimitive()
parentNodePath=sentNpToExtend.find("standAloneCubePolySet")
parentNodePath.attachNewNode(collNodeII)
#This is a method that draws faces whenever the geometry of the blue-cube is being extended (it also adds collisionPolygons):
def drawAFaceMod(self,*args):
#structure is:
#0->positional data.
#1->normal data.
#2->color data.
#3->uv data.
#4->primitive.
#5->current starting index for primitive.
vertexMod=args[0]
normalMod=args[1]
colorMod=args[2]
textureMod=args[3]
prim_dat=args[4]
numbr=args[5]
activeColourMod=args[6]
collNodeII=args[7]
currentArray=args[8]
setNum=0
setNum=numbr
opacityLevel=1
for specificPoint in currentArray:
vertexMod.setRow(setNum)
textureMod.setRow(setNum)
colorMod.setRow(setNum)
normalMod.setRow(setNum)
vertexMod.addData3f(specificPoint.x,specificPoint.y,specificPoint.z)
normalMod.addData3f(self.myNormalize(Vec3(2*specificPoint.x-1,2*specificPoint.y-1,2*specificPoint.z-1)))
gotColour=activeColourMod
colorMod.addData4f(gotColour.x, gotColour.y, gotColour.z, opacityLevel)
textureMod.addData2f(1, 1)
setNum+=1
#give the face a collision:
colQuadSolid=CollisionPolygon(currentArray[0],currentArray[1],currentArray[2],currentArray[3])
collNodeII.addSolid(colQuadSolid)
prim_dat.addVertices(numbr, numbr+1, numbr+2)
prim_dat.addVertices(numbr, numbr+2, numbr+3)
#This is a method that will return a cube upon being called:
def generateStandAloneCube(self,*args):
#0->colour.
#1->size.
#2->transparency state.
#3->collision state.
#4->lines
activeColorGenUse=args[0]
colourUse=args[0]
sizeSet=args[1]
transparencyState=args[2]
collisionState=args[3]
array = GeomVertexArrayFormat()
array.addColumn(InternalName.make('vertex'), 3,Geom.NTFloat32, Geom.CPoint)
array.addColumn(InternalName.make('texcoord'), 2,Geom.NTFloat32, Geom.CTexcoord)
array.addColumn(InternalName.make('normal'), 3,Geom.NTFloat32, Geom.CNormal)
array.addColumn(InternalName.make('color'), 4,Geom.NTFloat32, Geom.CColor)
format = GeomVertexFormat()
format.addArray(array)
format = GeomVertexFormat.registerFormat(format)
node = GeomNode("standAloneCube")
collNodeGen='n'
if(collisionState):
collNodeGen=CollisionNode("blockTestCollision")
#self.collNodeGen=CollisionNode("blockTestCollision")
#the writers and geom and primitive:
vdata = GeomVertexData('VertexData', format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
color = GeomVertexWriter(vdata, 'color')
texcoord = GeomVertexWriter(vdata, 'texcoord')
tileGeom=Geom(vdata)
tileGeom.setBoundsType (3)
prim = GeomTriangles(Geom.UHStatic)
counter=0
#okay, now, to draw the faces:
genericCubeListPoints=[]
sendDimensions=Point3(self.blockDimensions.x*sizeSet.x,self.blockDimensions.y*sizeSet.y,self.blockDimensions.z*sizeSet.z)
genericCubeListPoints=self.generateCubeGeneric(Point3(0,0,0),sendDimensions,[])
for currentArray in genericCubeListPoints:
self.drawAGenericFace(vertex,normal,color,texcoord,prim,counter,collisionState,activeColorGenUse,collNodeGen,currentArray)
counter+=4
prim.closePrimitive()
tileGeom.addPrimitive(prim)
node.addGeom(tileGeom)
gotProcGeom = render.attachNewNode(node)
gotProcGeom.setName("standAloneCube"+str(gotProcGeom.node().this))
if(collisionState):
parentNodePath=gotProcGeom.attachNewNode("standAloneCubePolySet")
cn=parentNodePath.attachNewNode(collNodeGen)
#self.worldCursorNp=gotProcGeom
gotProcGeom.setTransparency(TransparencyAttrib.MAlpha)
if(transparencyState):
gotProcGeom.setAttrib(DepthOffsetAttrib.make(1))
gotProcGeom.setBin("fixed", 0)
del genericCubeListPoints[:]
return gotProcGeom
#This is a method that simply draws a face that will belong to a cube (it also adds collisionPolygons):
def drawAGenericFace(self,*args):
#structure is:
#0->positional data.
#1->normal data.
#2->color data.
#3->uv data.
#4->primitive.
#5->current starting index for primitive.
vertex=args[0]
normal=args[1]
color=args[2]
texcoord=args[3]
prim_dat=args[4]
numbr=args[5]
collisionState=args[6]
activeColorGenUse=args[7]
collNodeGen=args[8]
currentArray=args[9]
for specificPoint in currentArray:
vertex.addData3f(specificPoint.x,specificPoint.y,specificPoint.z)
normal.addData3f(self.myNormalize(Vec3(2*specificPoint.x-1,2*specificPoint.y-1,2*specificPoint.z-1)))
pointColor=activeColorGenUse
color.addData4f(pointColor.x, pointColor.y, pointColor.z, pointColor.w)
texcoord.addData2f(1, 1)
#give the face a collisionSolid:
if(collisionState):
colQuadSolid=CollisionPolygon(currentArray[0],currentArray[1],currentArray[2],currentArray[3])
collNodeGen.addSolid(colQuadSolid)
prim_dat.addVertices(numbr, numbr+1, numbr+2)
prim_dat.addVertices(numbr, numbr+2, numbr+3)
#This is a method that returns a nested list which contains the points that make up the faces of a cube:
def generateCubeGeneric(self,originPoint,cubeDimension,exemptFaces):
genericCubeListPoints=[]
for i in range(1,7,1):
if (i not in exemptFaces):
#draw it:
gotArray=self.returnProperFaceCoordinates(originPoint,cubeDimension,i)
genericCubeListPoints.append(gotArray)
return genericCubeListPoints
#This returns the points within a list that make up the face of a cube.
def returnProperFaceCoordinates(self,*args):
#0->(xOrigin,yOrigin,zOrigin)
#1->(xDimension,yDimension,zDimension)
#2->side to draw: 1,2,3,4,5,6: front,back,left,right,top,bottom
originPoint=args[0]
dimensionData=args[1]
sideToDraw=args[2]
if(sideToDraw==1):
#drawing the front:
point1=Point3(originPoint.x,originPoint.y,originPoint.z)
point2=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
point3=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
point4=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
elif(sideToDraw==2):
#drawing the back:
point1=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
point2=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
point3=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
point4=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
elif(sideToDraw==3):
#drawing the left:
point1=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
point2=Point3(originPoint.x,originPoint.y,originPoint.z)
point3=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
point4=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
elif(sideToDraw==4):
#drawing the right:
point1=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
point2=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
point3=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
point4=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
elif(sideToDraw==5):
#drawing the top:
point1=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
point2=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
point3=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
point4=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
elif(sideToDraw==6):
#drawing the bottom:
point1=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
point2=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
point3=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
point4=Point3(originPoint.x,originPoint.y,originPoint.z)
return [point1,point2,point3,point4]
#This just normalizes the vector that will be used for the normals of the points that make up the cube:
def myNormalize(self,myVec):
myVec.normalize()
return myVec
runMe=run_me()
runMe.run()
To recap, this is what causes the bug: the collider is added to a collisionTraverser within the main thread. The geometry of the model is modified within an asyncTask. The application freezes whenever this happens.
The application does not freeze:
- If nothing is added to the collisionTraverser and the geometry is modified within an asyncTask.
- If whatever is added to the collisionTraverser does not have any collisionSolids, i.e. if the collisionNode is empty and no collisionBox, etc.,is added to it. If the geometry is modified within an asyncTask, the application does not freeze.
I hope this helps. Also, when the blue-cube in the test-case is modified, collisionSolids are also added to it, just in case this information proves useful. I’m on a windows 10 machine. Is something wrong with the code, or is it a bug with Panda3d itself?
(What I’m trying to achieve in the main app I’m working on is the ability to modify a model’s geometry within an asyncTask, e.g. terrain geometry, character models, etc.)
If anything is unclear about this or the code, please tell me and I’ll clarify.
Thanks.