openVRengine.py

You can view and download this file on Github: openVRengine.py

  1#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  2# This is an EXUDYN example
  3#
  4# Details:  test creating piston engine with variable number of pistons and piston angles;
  5#           possibility to interact with openVR
  6#
  7# Author:   Johannes Gerstmayr
  8# Date:     2023-01-17
  9#
 10# Copyright:This file is part of Exudyn. Exudyn is free software. You can redistribute it and/or modify it under the terms of the Exudyn license. See 'LICENSE.txt' for more details.
 11#
 12#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 13
 14
 15import exudyn as exu
 16from exudyn.utilities import * #includes itemInterface and rigidBodyUtilities
 17import exudyn.graphics as graphics #only import if it does not conflict
 18from math import sin, cos, asin, acos, pi, exp, log, tan, atan, radians
 19from exudyn.interactive import InteractiveDialog
 20
 21
 22omegaDrive = 4*pi*0.5
 23tEnd = 3600
 24nodeType = exu.NodeType.RotationEulerParameters
 25fixedSpeed = False #if false, the speed is given only for first 1 second
 26
 27# nodeType = exu.NodeType.RotationRxyz
 28#nodeType = exu.NodeType.RotationRotationVector
 29
 30# import matplotlib.pyplot as plt
 31# plt.close('all')
 32zOffAdd = -0.5
 33
 34class EngineParameters:
 35    def __init__(self, crankAnglesDegrees=[], pistonAnglesDegrees=[]):
 36        #parameters in m, s, kg, rad, ...
 37        self.crankAnglesDegrees = crankAnglesDegrees
 38        if pistonAnglesDegrees == []:
 39            self.pistonAnglesDegrees = list(0*np.array(crankAnglesDegrees))
 40        else:
 41            self.pistonAnglesDegrees = pistonAnglesDegrees
 42
 43        crankAngles = pi/180*np.array(crankAnglesDegrees)
 44        self.crankAngles = list(crankAngles)
 45
 46        pistonAngles = pi/180*np.array(self.pistonAnglesDegrees)
 47        self.pistonAngles = list(pistonAngles)
 48
 49        densitySteel = 7850
 50        #kinematics & inertia & drawing
 51        fZ = 1#0.2
 52        self.pistonDistance = 0.08
 53        self.pistonMass = 0.5
 54        self.pistonLength = 0.05
 55        self.pistonRadius = 0.02
 56
 57        self.conrodLength = 0.1 #X
 58        self.conrodHeight = 0.02*fZ#Y
 59        self.conrodWidth = 0.02*fZ #Z
 60        self.conrodRadius = 0.012*fZ #Z
 61
 62        self.crankArmLength = 0.04      #X
 63        self.crankArmHeight = 0.016     #Y
 64        self.crankArmWidth = 0.01*fZ       #Z width of arm
 65        self.crankBearingWidth = 0.012*fZ   #Z
 66        self.crankBearingRadius = 0.01
 67
 68        self.conrodCrankCylLength = 0.024*fZ  #Z; length of cylinder (bearing conrod-crank)
 69        self.conrodCrankCylRadius = 0.008 #radius of cylinder (bearing conrod-crank)
 70
 71        self.pistonDistance = self.crankBearingWidth + 2*self.crankArmWidth + self.conrodCrankCylLength #Z distance
 72
 73        self.inertiaConrod = InertiaCuboid(densitySteel, sideLengths=[self.conrodLength, self.conrodHeight, self.conrodWidth])
 74
 75        eL = self.Length()
 76        #last bearing:
 77        densitySteel2 = densitySteel
 78        self.inertiaCrank = InertiaCylinder(densitySteel2, self.crankBearingWidth, self.crankBearingRadius, axis=2).Translated([0,0,0.5*eL-0.5*self.crankBearingWidth])
 79
 80
 81
 82        for cnt, angle in enumerate(self.crankAngles):
 83            A = RotationMatrixZ(angle)
 84            zOff = -0.5*eL + cnt*self.pistonDistance
 85            arm = InertiaCuboid(densitySteel2, sideLengths=[self.crankArmLength, self.crankArmHeight, self.crankArmWidth])
 86            cylCrank = InertiaCylinder(densitySteel2, self.crankBearingWidth, self.crankBearingRadius, axis=2)
 87            cylConrod = InertiaCylinder(densitySteel2, self.conrodCrankCylLength, self.conrodCrankCylRadius, axis=2)
 88            #add inertias:
 89            self.inertiaCrank += cylCrank.Translated([0,0,zOff+self.crankBearingWidth*0.5])
 90            self.inertiaCrank += arm.Rotated(A).Translated(A@[self.crankArmLength*0.5,0,zOff+self.crankBearingWidth+self.crankArmWidth*0.5])
 91            self.inertiaCrank += cylConrod.Translated(A@[self.crankArmLength,0,zOff+self.crankBearingWidth+self.crankArmWidth+self.conrodCrankCylLength*0.5])
 92            self.inertiaCrank += arm.Rotated(A).Translated(A@[self.crankArmLength*0.5,0,zOff+self.crankBearingWidth+self.crankArmWidth*1.5+self.conrodCrankCylLength])
 93
 94        # self.inertiaCrank = InertiaCylinder(1e-8*densitySteel, length=self.pistonLength,
 95        #                                      outerRadius=self.pistonRadius, innerRadius=0.5*self.pistonRadius, axis=2)
 96
 97        self.inertiaPiston = InertiaCylinder(densitySteel, length=self.pistonLength,
 98                                             outerRadius=self.pistonRadius, innerRadius=0.5*self.pistonRadius, axis=0)
 99
100        #self.inertiaCrank.com = [0,0,0]
101        # print('crank COM=',np.array(self.inertiaCrank.com).round(8))
102        # print('inertiaCrank=',self.inertiaCrank)
103        # print('inertiaConrod=',self.inertiaConrod)
104        # print('inertiaPiston=',self.inertiaPiston)
105
106    def Length(self):
107        return self.pistonDistance*len(self.crankAngles) + self.crankBearingWidth
108
109    def MaxDimX(self):
110        return self.crankArmLength + self.conrodLength + self.pistonLength
111
112def ComputeSliderCrank(angleCrank, anglePiston, l1, l2):
113    phi1 = angleCrank-anglePiston
114    h = l1*sin(phi1) #height of crank-conrod bearing
115    phi2 = asin(h/l2) #angle of conrod in 2D slider-crank, corotated with piston rotation
116    angleConrod = anglePiston-phi2
117    Acr = RotationMatrixZ(angleConrod)
118    dp = l1*cos(phi1) + l2*cos(phi2) #distance of piston from crank rotation axis
119    return [phi1,phi2, angleConrod, Acr, dp]
120
121
122#this function (re-)creates gear geometry
123def CreateEngine(P):
124
125    colorCrank = graphics.color.grey
126    colorConrod = graphics.color.dodgerblue
127    colorPiston = graphics.color.brown[0:3]+[0.5]
128    showJoints = True
129
130    gravity = [0,-9.81*0,0]
131    eL = P.Length()
132    oGround=mbs.AddObject(ObjectGround(referencePosition= [0,0,zOffAdd], visualization=VObjectGround(graphicsData= [])))
133    nGround=mbs.AddNode(NodePointGround(referenceCoordinates = [0,0,zOffAdd]))
134
135    gEngine = [graphics.Brick(centerPoint=[0,0,0], size=[P.MaxDimX()*2, P.MaxDimX(), eL*1.2],
136                                          color=[0.6,0.6,0.6,0.1], addEdges=True,
137                                          edgeColor = [0.8,0.8,0.8,0.3], addFaces=False)]
138    gEngine = [] #no block
139    #oEngine=mbs.AddObject(ObjectGround(referencePosition= [0,0,0], visualization=VObjectGround(graphicsData= gEngine)))
140    [nEngine, oEngine] = AddRigidBody(mbs, InertiaCuboid(1000, sideLengths=[1,1,1]), #dummy engine inertia
141                                      nodeType = nodeType,
142                                      position=[0,0,zOffAdd],
143                                      graphicsDataList = gEngine
144                                      )
145
146    mGround = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oGround))
147    mEngine = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oEngine))
148    sEngineForce = 0
149    oEngineJoint = 0
150    sEngineTorque  = 0
151    oEngineJoint = mbs.AddObject(GenericJoint(markerNumbers=[mEngine, mGround], constrainedAxes=[1,1,1, 1,1,1],
152                                    visualization=VGenericJoint(show=False)))
153    sEngineForce = mbs.AddSensor(SensorObject(objectNumber=oEngineJoint, storeInternal=True,
154                                              outputVariableType=exu.OutputVariableType.ForceLocal))
155    sEngineTorque = mbs.AddSensor(SensorObject(objectNumber=oEngineJoint, storeInternal=True,
156                                              outputVariableType=exu.OutputVariableType.TorqueLocal))
157
158    bConrodList = []
159    bPistonList = []
160    gCrank = []
161    for cnt, angleCrank in enumerate(P.crankAngles):
162        anglePiston = P.pistonAngles[cnt]
163        Ac = RotationMatrixZ(angleCrank)
164        Ap = RotationMatrixZ(anglePiston)
165        [phi1,phi2, angleConrod, Acr, dp] = ComputeSliderCrank(angleCrank, anglePiston, P.crankArmLength, P.conrodLength)
166
167        zOff = -0.5*eL + cnt*P.pistonDistance + zOffAdd
168        #zOff = 0
169        #crank bearing
170        zAdd = 0
171        if cnt>0: zAdd = P.crankArmWidth
172        gCrank += [graphics.Cylinder(pAxis=[0,0,zOff-zAdd], vAxis=[0,0,P.crankBearingWidth+P.crankArmWidth+zAdd],
173                                        radius=P.crankBearingRadius, color=graphics.color.red)]
174        #arm1
175        arm1 = graphics.Brick([P.crankArmLength*0.5,0,zOff+P.crankArmWidth*0.5+P.crankBearingWidth],
176                                              size=[P.crankArmLength,P.crankArmHeight,P.crankArmWidth], color=colorCrank)
177        gCrank += [graphics.Move(arm1, [0,0,0], Ac)]
178        #conrod bearing
179        gCrank += [graphics.Cylinder(pAxis=Ac@[P.crankArmLength,0,zOff+P.crankBearingWidth+P.crankArmWidth*0],
180                                       vAxis=[0,0,P.conrodCrankCylLength+2*P.crankArmWidth], radius=P.conrodCrankCylRadius, color=colorCrank)]
181
182        #arm2
183        arm2 = graphics.Brick([P.crankArmLength*0.5,0,zOff+P.crankArmWidth*1.5+P.crankBearingWidth+P.conrodCrankCylLength],
184                                              size=[P.crankArmLength,P.crankArmHeight,P.crankArmWidth],
185                                              color=colorCrank)
186        gCrank += [graphics.Move(arm2, [0,0,0], Ac)]
187
188        if cnt == len(P.crankAngles)-1:
189            gCrank += [graphics.Cylinder(pAxis=[0,0,zOff+P.crankArmWidth+P.crankBearingWidth+P.conrodCrankCylLength], vAxis=[0,0,P.crankBearingWidth+P.crankArmWidth],
190                                            radius=P.crankBearingRadius, color=graphics.color.red)]
191
192        #++++++++++++++++++++++++++++++++++++++
193        #conrod
194        gConrod = [ graphics.RigidLink(p0=[-0.5*P.conrodLength, 0, 0], p1=[0.5*P.conrodLength,0,0], axis0= [0,0,1], axis1= [0,0,1],
195                                           radius= [P.conrodRadius]*2,
196                                           thickness= P.conrodHeight, width=[P.conrodWidth]*2, color= colorConrod, nTiles= 16)]
197
198        [nConrod, bConrod] = AddRigidBody(mbs, P.inertiaConrod,
199                                          nodeType = nodeType,
200                                        position=Ac@[P.crankArmLength,0,0] + Acr@[0.5*P.conrodLength,0,
201                                                  zOff+P.crankArmWidth+P.crankBearingWidth+0.5*P.conrodCrankCylLength],
202                                        # angularVelocity=[0,0,0],
203                                        rotationMatrix=Acr,
204                                        gravity = gravity,
205                                        graphicsDataList = gConrod
206                                        )
207        bConrodList += [bConrod]
208        #++++++++++++++++++++++++++++++++++++++
209        #piston
210        gPiston = [graphics.Cylinder(pAxis=[-P.conrodRadius*0.5,0,0],
211                                         vAxis=[P.pistonLength,0,0], radius=P.pistonRadius, color=colorPiston)]
212
213        [nPiston, bPiston] = AddRigidBody(mbs, P.inertiaPiston,
214                                          nodeType = nodeType,
215                                        # position=Ap@[P.crankArmLength + P.conrodLength,0,
216                                        #           zOff+P.crankArmWidth+P.crankBearingWidth+0.5*P.conrodCrankCylLength],
217                                        position=Ap@[dp,0,
218                                                  zOff+P.crankArmWidth+P.crankBearingWidth+0.5*P.conrodCrankCylLength],
219                                        # angularVelocity=[0,0,0],
220                                        rotationMatrix=Ap,
221                                        gravity = gravity,
222                                        graphicsDataList = gPiston
223                                        )
224        bPistonList += [bPiston]
225
226    [nCrank, bCrank] = AddRigidBody(mbs, P.inertiaCrank,
227                                    nodeType = nodeType,
228                                    position=[0,0,0],
229                                    #angularVelocity=[0,0,omega0],
230                                    gravity = gravity,
231                                    graphicsDataList = gCrank
232                                    )
233
234    sCrankAngVel = mbs.AddSensor(SensorNode(nodeNumber=nCrank, storeInternal=True,
235                                              outputVariableType=exu.OutputVariableType.AngularVelocity))
236
237    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
238    #JOINTS:
239    [oJointCrank, mBody0Crank, mBody1Crank] = AddRevoluteJoint(mbs, oEngine, bCrank, point=[0,0,-0.5*eL], axis=[0,0,1], showJoint=showJoints,
240                                                axisRadius=P.crankBearingRadius*1.2, axisLength=P.crankBearingWidth*0.8)
241
242    for cnt, angleCrank in enumerate(P.crankAngles):
243        anglePiston = P.pistonAngles[cnt]
244        Ac = RotationMatrixZ(angleCrank)
245        Ap = RotationMatrixZ(anglePiston)
246        [phi1,phi2, angleConrod, Acr, dp] = ComputeSliderCrank(angleCrank, anglePiston, P.crankArmLength, P.conrodLength)
247
248        zOff = -0.5*eL + cnt*P.pistonDistance
249        #zOff = 0
250
251        [oJointCC, mBody0CC, mBody1CC] = AddRevoluteJoint(mbs, bCrank, bConrodList[cnt],
252                                                          point=Ac@[P.crankArmLength,0,zOff + P.crankBearingWidth+P.crankArmWidth+0.5*P.conrodCrankCylLength],
253                                                          axis=[0,0,1], showJoint=showJoints,
254                                                          axisRadius=P.crankBearingRadius*1.3, axisLength=P.crankBearingWidth*0.8)
255
256        #pPiston = A@[P.crankArmLength+P.conrodLength,0,zOff + P.crankBearingWidth+P.crankArmWidth+0.5*P.conrodCrankCylLength]
257        pPiston = Ap@[dp,0,zOff + P.crankBearingWidth+P.crankArmWidth+0.5*P.conrodCrankCylLength]
258        [oJointCP, mBody0CP, mBody1CP] = AddRevoluteJoint(mbs, bConrodList[cnt], bPistonList[cnt],
259                                                          point=pPiston,
260                                                          axis=[0,0,1], showJoint=showJoints,
261                                                          axisRadius=P.crankBearingRadius*1.3, axisLength=P.crankBearingWidth*0.8)
262
263        # AddPrismaticJoint(mbs, oEngine, bPistonList[cnt],
264        #                                                 point=pPiston,
265        #                                                 axis=A@[1,0,0], showJoint=showJoints,
266        #                                                 axisRadius=P.crankBearingRadius*1.3, axisLength=P.crankBearingWidth*0.8)
267        mEngine = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oEngine, localPosition=pPiston))
268        mPiston = mbs.AddMarker(MarkerBodyRigid(bodyNumber=bPistonList[cnt], localPosition=[0,0,0]))
269        mbs.AddObject(GenericJoint(markerNumbers=[mPiston, mEngine], constrainedAxes=[0,1,0, 0,0,1],
270                                   # rotationMarker0=A.T,
271                                   rotationMarker1=Ap,
272                                   visualization=VGenericJoint(show=False, axesRadius=P.conrodRadius*1.4,axesLength=0.05)))
273
274    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
275    #DRIVE:
276    def UFoffset(mbs, t, itemNumber, lOffset):
277        return 0
278
279    def UFoffset_t(mbs, t, itemNumber, lOffset): #time derivative of UFoffset
280        return SmoothStep(t, 0, 0.5, 0, omegaDrive)
281
282    mCrankRotation = mbs.AddMarker(MarkerNodeRotationCoordinate(nodeNumber=nCrank, rotationCoordinate=2))
283    mNodeEngine = mbs.AddMarker(MarkerNodeRotationCoordinate(nodeNumber=nEngine, rotationCoordinate=2))
284    oRotationConstraint = mbs.AddObject(CoordinateConstraint(markerNumbers=[mNodeEngine, mCrankRotation], velocityLevel=True,
285                                        offsetUserFunction=UFoffset,
286                                        offsetUserFunction_t=UFoffset_t,
287                                        visualization=VCoordinateConstraint(show=False)))
288
289    return [oEngine, oEngineJoint, sEngineForce, sEngineTorque, sCrankAngVel, oRotationConstraint, nCrank, bCrank]
290
291engines = []
292engines+=[EngineParameters([0])]                                           #R1
293engines+=[EngineParameters([0,180])]                                       #R2
294engines+=[EngineParameters([0,180,180,0])]                                 #R4 straight-four engine, Reihen-4-Zylinder
295engines+=[EngineParameters([0,90,270,180])]                                #R4 in different configuration
296engines+=[EngineParameters([0,180,180,0],[0,180,180,0])]                   #Boxer 4-piston perfect mass balancing
297
298engines+=[EngineParameters([0,120,240])]                                   #R3
299engines+=[EngineParameters(list(np.arange(0,5)*144))]                      #R5
300engines+=[EngineParameters([0,120,240,240,120,0])]                         #R6
301engines+=[EngineParameters([0,0,120,120,240,240],[-30,30,-30,30,-30,30])]  #V6
302engines+=[EngineParameters([0,0,120,120,240,240,240,240,120,120,0,0],[-30,30,-30,30,-30,30,30,-30,30,-30,30,-30])] #V12
303
304engines+=[EngineParameters([0,90,180,270,270,180,90,360])]                  #R8
305engines+=[EngineParameters([0,0,90,90,270,270,180,180], [-45,45,-45,45, 45,-45,45,-45])] #V8
306
307# n=12
308# a=list(np.arange(0,n)*30)
309# b=list(np.arange(n-1,-1,-1)*30)
310# #engines+=[EngineParameters(a+a,b+b)
311
312#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
313#
314engines=[EngineParameters([0,90,270,180], [90]*4)]
315#engines=[EngineParameters([0,0,120,120,240,240,240,240,120,120,0,0],[60,120,60,120,60,120,120,60,120,60,120,60])] #V12
316
317for engine in engines:
318
319    SC = exu.SystemContainer()
320    mbs = SC.AddSystem()
321
322    [oEngine, oEngineJoint, sEngineForce, sEngineTorque, sCrankAngVel, oRotationConstraint,
323     nCrank, bCrank] = CreateEngine(engine)
324
325    d = 2.4     #box size
326    h = 0.5*d #box half size
327    w = d
328    gDataList = []
329    gDataList += [graphics.CheckerBoard(point=[0,0,-h], normal=[0,0,1], size=2*d, size2=d, nTiles=12*2, nTiles2=12, color=graphics.color.grey)]
330    gDataList += [graphics.CheckerBoard(point=[-w,0,0], normal=[ 1,0,0], size=d, nTiles=12, color=graphics.color.lightgrey)]
331    gDataList += [graphics.CheckerBoard(point=[ w,0,0], normal=[-1,0,0], size=d, nTiles=12, color=graphics.color.lightgrey)]
332    gDataList += [graphics.CheckerBoard(point=[0,-h,0], normal=[0,-1,0], size=2*d, size2=d, nTiles=12*2, nTiles2=12, color=graphics.color.dodgerblue)]
333    gDataList += [graphics.CheckerBoard(point=[0, h,0], normal=[0, 1,0], size=2*d, size2=d, nTiles=1, color=[0.8,0.8,1,1])]#, alternatingColor=[0.8,0.8,1,1])]
334    # gDataList += [graphics.CheckerBoard(point=[0, 0,h], normal=[0, 0,-1], size=d, nTiles=1, color=[0.8,0.8,0.8,0.9])]
335
336    oGround=mbs.AddObject(ObjectGround(referencePosition= [0,0,0],
337                                   visualization=VObjectGround(graphicsData=gDataList)))
338
339
340    def PreStepUF(mbs, t):
341        u = mbs.systemData.GetODE2Coordinates()
342
343        if not fixedSpeed and t >= 1: #at this point, the mechanism runs freely
344            mbs.SetObjectParameter(oRotationConstraint, 'activeConnector', False)
345
346        #mbs.systemData.SetODE2Coordinates(u)
347        return True
348
349    mbs.SetPreStepUserFunction(PreStepUF)
350
351
352    mbs.Assemble()
353
354    stepSize = 0.002
355    simulationSettings = exu.SimulationSettings() #takes currently set values or default values
356
357    simulationSettings.timeIntegration.numberOfSteps = int(tEnd/stepSize)
358    simulationSettings.timeIntegration.endTime = tEnd
359    # simulationSettings.timeIntegration.newton.relativeTolerance = 1e-8*0.01
360    # simulationSettings.timeIntegration.newton.absoluteTolerance = 1e-10*0.01
361    simulationSettings.timeIntegration.verboseMode = 1
362
363    # simulationSettings.timeIntegration.simulateInRealtime = True
364
365    simulationSettings.solutionSettings.solutionWritePeriod=0.01
366    simulationSettings.solutionSettings.sensorsWritePeriod = stepSize*10
367    simulationSettings.solutionSettings.writeSolutionToFile = False
368    #simulationSettings.solutionSettings.writeInitialValues = False #otherwise values are duplicated
369    #simulationSettings.solutionSettings.coordinatesSolutionFileName = 'solution/coordinatesSolution.txt'
370
371    simulationSettings.timeIntegration.generalizedAlpha.computeInitialAccelerations = False
372
373    simulationSettings.timeIntegration.generalizedAlpha.lieGroupAddTangentOperator = False
374    #simulationSettings.displayStatistics = True
375    # simulationSettings.displayComputationTime = True
376    simulationSettings.linearSolverType=exu.LinearSolverType.EigenSparse
377
378    #SC.visualizationSettings.nodes.defaultSize = 0.05
379
380    simulationSettings.solutionSettings.solutionInformation = "Engine"
381
382    SC.visualizationSettings.general.graphicsUpdateInterval = 0.01
383    #SC.visualizationSettings.general.drawWorldBasis = True
384    #SC.visualizationSettings.general.worldBasisSize = 0.1
385
386    SC.visualizationSettings.markers.show = False
387    SC.visualizationSettings.loads.show = False
388    SC.visualizationSettings.nodes.show = False
389    SC.visualizationSettings.connectors.show = False
390
391    SC.visualizationSettings.openGL.multiSampling = 4
392    SC.visualizationSettings.openGL.shadow = 0.3 #set to 0, if your graphics card cannot handle this!
393    SC.visualizationSettings.openGL.lineWidth = 3
394    SC.visualizationSettings.openGL.light0position = [0.25,1,3,0]
395
396    #++++++++++++++++++++++++++++++++
397    #openVR:
398    SC.visualizationSettings.general.drawCoordinateSystem = False
399    #good for openVR
400    SC.visualizationSettings.general.graphicsUpdateInterval = 0.005 #small enough to get large enough fps
401    simulationSettings.timeIntegration.simulateInRealtime = True
402
403    useOpenVR = False #set this true for openVR to run!!!
404    SC.visualizationSettings.window.renderWindowSize=[1176, 1320] # this needs to fit to your VR HMD (Head Mounted Display) settings (will show in console when openVR is started and openVR.logLevel is large enough!)
405    if useOpenVR:
406        SC.visualizationSettings.openGL.initialZoom = 1# 0.4*20 #0.4*max scene size
407        #SC.visualizationSettings.openGL.initialCenterPoint = [0,0,2]
408        SC.visualizationSettings.general.autoFitScene = False
409        SC.visualizationSettings.window.limitWindowToScreenSize = False #this allows a larger window size than your monitor can display in case!
410        SC.visualizationSettings.window.startupTimeout = 100000 #if steam / VRidge, etc. not found
411        SC.visualizationSettings.interactive.openVR.enable = True
412        SC.visualizationSettings.interactive.lockModelView = True #lock rotation/translation/zoom of model
413        SC.visualizationSettings.interactive.openVR.logLevel = 3
414        SC.visualizationSettings.interactive.openVR.actionManifestFileName = "C:/DATA/cpp/DocumentationAndInformation/openVR/hellovr_actions.json"
415
416    #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
417
418
419    SC.visualizationSettings.general.autoFitScene = False #use loaded render state
420    exu.StartRenderer()
421    cws = SC.GetRenderState()['currentWindowSize']
422    print('window size=', cws, '(check that this is according to needs of Head Mounted Display)')
423    # if 'renderState' in exu.sys:
424    #     SC.SetRenderState(exu.sys[ 'renderState' ])
425
426    mbs.SolveDynamic(simulationSettings)
427
428    exu.StopRenderer() #safely close rendering window!