rendererNOGLFWexample.py
You can view and download this file on Github: rendererNOGLFWexample.py
1#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2# This is an EXUDYN example
3#
4# Details: An example for offline rendering and merging of images with a ball jumping on ground
5#
6# Author: Johannes Gerstmayr
7# Date: 2026-02-07
8#
9# 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.
10#
11#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12
13import exudyn as exu
14#a local run must not open a plot window when windows are suppressed (#2477)
15if exu.special.userInterface.suppressPlots: import matplotlib; matplotlib.use('Agg')
16from exudyn.utilities import InertiaSphere, MarkerBodyRigid, SensorBody
17import exudyn.graphics as graphics
18import numpy as np
19
20useGraphics = False # without test
21
22testSolution = 0
23
24SC = exu.SystemContainer()
25mbs = SC.AddSystem()
26
27
28radius=0.06
29mass = 0.2 #mass in kg
30contactStiffness = 2e5*10 #stiffness of spring-damper in N/m
31contactDamping = 1e-4*contactStiffness #damping constant in N/(m/s); as we have always contact, we only need some damping for initial effects
32dynamicFriction = 0.3
33
34isExplicitSolver = False
35tEnd = 2.5 #end time of simulation
36
37stepSize = 5e-4 #*10
38
39g = 9.81
40
41size = 2.5
42
43quadEdges = graphics.Lines([[-0.5*size,-0.5*size,0],
44 [ 0.5*size,-0.5*size,0],
45 [ 0.5*size, 0.5*size,0],
46 [-0.5*size, 0.5*size,0],
47 [-0.5*size,-0.5*size,0]],color=[0.9,0.9,0.9,1]
48 )
49oGround = mbs.CreateGround(graphicsDataList=[quadEdges,graphics.CheckerBoard(point=[0,0,0],size=size,
50 color=graphics.color.lightgrey[0:3]+[graphics.material.indexChrome],
51 alternatingColor=graphics.color.lightgrey2[0:3]+[graphics.material.indexChrome],
52 ), ])
53mGround = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oGround))
54
55
56nSpheres = 5
57
58x = -1
59y = 0
60z = radius+1
61vx = 1
62vy = 0
63vz = 2
64
65oMass = mbs.CreateRigidBody(referencePosition=[x,y,z],
66 initialVelocity=[vx,vy,vz],
67 initialAngularVelocity=[0,0,0],
68 nodeType=exu.NodeType.RotationEulerParameters,
69 inertia=InertiaSphere(mass=mass, radius=radius),
70 gravity = [0,0,-g],
71 graphicsDataList=[graphics.Sphere(radius=radius,
72 color=graphics.color.dodgerblue,
73 nTiles=32)],
74 )
75mMass = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oMass))
76
77quadPoints = exu.Vector3DList([[-size,-size,0],[size,-size,0],[size,size,0],[-size,size,0]])
78oSSC = mbs.CreateSphereQuadContact(itemNumbers=[oMass, oGround],
79 quadPoints=quadPoints,
80 includeEdges=15, #all edges
81 sphereRadius=radius,
82 contactStiffness = contactStiffness,
83 contactDamping = contactDamping,
84 dynamicFriction=dynamicFriction,
85 show = True
86 )
87sPos=mbs.AddSensor(SensorBody(bodyNumber=oMass, storeInternal=True,
88 outputVariableType=exu.OutputVariableType.Position))
89
90
91#exu.Print(mbs)
92mbs.Assemble()
93
94simulationSettings = exu.SimulationSettings()
95simulationSettings.solution.file.write = True
96simulationSettings.solution.file.writePeriod = 0.02
97simulationSettings.solution.sensors.writePeriod = 0.001 #output interval
98simulationSettings.timeIntegration.numberOfSteps = int(tEnd/stepSize)
99simulationSettings.timeIntegration.endTime = tEnd
100#simulationSettings.timeIntegration.realtime.active = True
101simulationSettings.timeIntegration.newton.absoluteTolerance = 1e-6
102simulationSettings.timeIntegration.newton.relativeTolerance = 1e-6
103
104simulationSettings.timeIntegration.stepInformation = 3 #remove flag 64 which shows step reduction warnings
105
106
107simulationSettings.timeIntegration.newton.useModifiedNewton = True
108simulationSettings.linearSolver.solverType = exu.LinearSolverType.EigenSparse
109
110simulationSettings.show.statistics = True
111simulationSettings.timeIntegration.verboseMode = 1
112SC.visualizationSettings.openGL.lineWidth = 2
113SC.visualizationSettings.view0.scene.drawCoordinateSystem = False
114SC.visualizationSettings.general.showSolverInformation = False
115SC.visualizationSettings.connectors.showContact = False
116SC.visualizationSettings.nodes.showBasis = True
117SC.visualizationSettings.nodes.basisSize = 1.25*radius
118
119SC.visualizationSettings.loads.show = False
120
121SC.visualizationSettings.view0.window.showComputationInfo = False
122SC.visualizationSettings.general.backgroundColor = [0,0,0,1]
123
124SC.visualizationSettings.openGL.multiSampling = 2
125SC.visualizationSettings.view0.window.renderWindowSize = [700*3,400*3]
126SC.visualizationSettings.raytracer.numberOfThreads = 16
127SC.visualizationSettings.raytracer.maxReflectionDepth = 0
128SC.visualizationSettings.raytracer.maxTransparencyDepth = 0
129
130if useGraphics:
131 SC.renderer.Start() #start graphics visualization
132
133SC.renderer.ZoomAll() #before first call to RedrawAndGetImage
134SC.renderer.SetModelView(zoom=0.85763,
135 rotationVector=[-1.570796,0,0],
136 centerPoint=[-0.1946863,0.7006764,0.6388125])
137
138if useGraphics:
139 SC.renderer.DoIdleTasks() #wait for pressing SPACE bar to continue
140
141mbs.variables['dt'] = 0.075
142mbs.variables['lastRedraw'] = -mbs.variables['dt']
143mbs.variables['listImages'] = []
144def PreStepUserFunction(mbs, t):
145 """User function to store images."""
146 dt = mbs.variables['dt']
147 if t - mbs.variables['lastRedraw'] >= dt-1e-6:
148 mbs.variables['lastRedraw'] += dt
149 image = SC.renderer.RedrawAndGetImage(True)
150 mbs.variables['listImages'].append(image)
151 print('get image:', t)
152
153 return True
154
155if not useGraphics:
156 mbs.SetPreStepUserFunction(PreStepUserFunction)
157
158
159mbs.SolveDynamic(simulationSettings)
160
161if useGraphics:
162 SC.renderer.Stop() #safely close rendering window!
163else:
164 import matplotlib.pyplot as plt
165 if False:
166 for image in mbs.variables['listImages']:
167 plt.imshow(image)
168 plt.show(block=True)
169
170 def merge_images(image_list):
171 """Merges a list of RGB images by treating black (0,0,0) as transparent."""
172 if not image_list:
173 return None
174
175 # Initialize the canvas with the first image
176 merged = image_list[0].astype(np.uint8)
177
178 for i in range(1, len(image_list)):
179 # Element-wise maximum retains the non-black pixels
180 merged = np.maximum(merged, image_list[i])
181
182 return merged
183
184 image = merge_images(mbs.variables['listImages'])
185 plt.imshow(image)
186 plt.axis('off')
187 plt.show()
188 #make sure that directory exists:
189 plt.imsave("images/mergedImageBallContact.jpg", image)
190
191mbs.PlotSensor(sPos, components=[2])
192
193#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
194
195mbs.SolutionViewer()