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