├── .gitignore ├── README.md ├── LICENSE ├── engine.py └── engine_generator.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.mr 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Engine Generator 2 | 3 | ## This script is garbage 4 | I am releasing this script **as is** and I have no intention of actively maintaining or updating it. I wrote it just to experiment with huge engines and save myself some work for a YouTube video. A lot of you asked for it to be released so here it is. If you'd like to make modifications to the script, feel free to make a pull request. 5 | 6 | ## How to use the script 7 | ### Running the script 8 | 1. Install Python 3 on your computer if it isn't already installed 9 | 2. Open a terminal and run `py engine.py` 10 | 11 | ### Modifying parameters 12 | All engine parameters are specified in the `Engine` class. Understanding how this works will require some knowledge of programming and there isn't a specific procedure to follow. The best way to understand how the script to work is to analyze the `generate_i4()`, `generate_v24()` and `generate_v69()` functions located in the `engine.py` script. It should be clear how the script works from these examples. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ange Yaghi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /engine.py: -------------------------------------------------------------------------------- 1 | 2 | import engine_generator 3 | 4 | def generate_i4(): 5 | cylinders0 = [] 6 | cylinders = [0,2,3,1] 7 | 8 | for i in range(4): 9 | cylinders0.append(i) 10 | 11 | bank = engine_generator.Bank(cylinders0, 0) 12 | engine = engine_generator.Engine([bank], cylinders) 13 | engine.engine_name = "I4" 14 | engine.starter_torque = 400 15 | engine.chamber_volume = 70 16 | 17 | engine.generate() 18 | engine.write_to_file("i4.mr") 19 | 20 | 21 | def generate_v24(): 22 | cylinders0 = [] 23 | cylinders1 = [] 24 | cylinders = [] 25 | 26 | for i in range(12): 27 | cylinders0.append(i * 2) 28 | cylinders1.append(i * 2 + 1) 29 | cylinders += [i * 2, i * 2 + 1] 30 | 31 | b0 = engine_generator.Bank(cylinders0, -45) 32 | b1 = engine_generator.Bank(cylinders1, 45) 33 | engine = engine_generator.Engine([b0, b1], cylinders) 34 | engine.engine_name = "V24" 35 | engine.starter_torque = 400 36 | engine.crank_mass = 200 37 | 38 | engine.generate() 39 | engine.write_to_file("test.mr") 40 | 41 | def generate_v69(): 42 | cylinders0 = [] 43 | cylinders1 = [] 44 | cylinders = [] 45 | 46 | for i in range(34): 47 | cylinders0.append(i * 2) 48 | cylinders1.append(i * 2 + 1) 49 | cylinders += [i * 2, i * 2 + 1] 50 | 51 | cylinders0.append(68) 52 | cylinders.append(68) 53 | 54 | b0 = engine_generator.Bank(cylinders0, -34.5) 55 | b1 = engine_generator.Bank(cylinders1, 34.5) 56 | b1.flip = True 57 | engine = engine_generator.Engine([b0, b1], cylinders) 58 | engine.engine_name = "V69" 59 | engine.starter_torque = 10000 60 | engine.crank_mass = 2000 61 | engine.bore = 197.9 62 | engine.stroke = 197.9 63 | engine.chamber_volume = 3000 64 | engine.rod_length = engine.stroke * 1.75 65 | engine.simulation_frequency = 1200 66 | engine.max_sle_solver_steps = 4 67 | engine.fluid_simulation_steps = 4 68 | engine.idle_throttle_plate_position = 0.9 69 | 70 | engine.generate() 71 | engine.write_to_file("v69_engine.mr") 72 | 73 | if __name__ == "__main__": 74 | generate_i4() 75 | -------------------------------------------------------------------------------- /engine_generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import io 3 | 4 | class Fuel: 5 | def __init__(self): 6 | # Gasoline 7 | self.molecular_mass = 100 8 | self.energy_density = 48.1 9 | self.density = 0.755 10 | self.molecular_afr = 25 / 2.0 11 | self.max_burning_efficiency = 0.8 12 | self.burning_efficiency_randomness = 0.5 13 | self.low_efficiency_attenuation = 0.6 14 | self.max_turbulence_effect = 2 15 | self.max_dilution_effect = 10 16 | 17 | def generate(self): 18 | return """molecular_mass: {} * units.g, 19 | energy_density: {} * units.kJ / units.g, 20 | density: {} * units.kg / units.L, 21 | molecular_afr: {}, 22 | max_burning_efficiency: {}, 23 | burning_efficiency_randomness: {}, 24 | low_efficiency_attenuation: {}, 25 | max_turbulence_effect: {}, 26 | max_dilution_effect: {}""".format(self.molecular_mass, self.energy_density, self.density, self.molecular_afr, self.max_burning_efficiency, self.burning_efficiency_randomness, self.low_efficiency_attenuation, self.max_turbulence_effect, self.max_dilution_effect) 27 | 28 | class Camshaft: 29 | def __init__(self): 30 | self.lobes = [] 31 | 32 | class Bank: 33 | def __init__(self, cylinder_numbers, bank_angle): 34 | self.cylinders = cylinder_numbers 35 | self.bank_angle = bank_angle 36 | 37 | self.camshaft = Camshaft() 38 | self.camshaft.lobes = [0] * len(self.cylinders) 39 | 40 | self.flip = False 41 | 42 | def get_cylinder_index(self, cylinder): 43 | return self.cylinders.index(cylinder) 44 | 45 | class Transmission: 46 | def __init__(self, gears): 47 | self.gears = gears 48 | self.node_name = "generated_transmission" 49 | 50 | self.max_clutch_torque = 1000 51 | 52 | class Vehicle: 53 | def __init__(self): 54 | self.node_name = "generated_vehicle" 55 | 56 | self.mass = 798 57 | self.drag_coefficient = 0.9 58 | self.cross_sectional_area = [72, 36] 59 | 60 | self.diff_ratio = 4.10 61 | self.tire_radius = 9 62 | self.rolling_resistance = 200 63 | 64 | class Engine: 65 | def __init__(self, banks, firing_order): 66 | self.banks = banks 67 | self.fuel = Fuel() 68 | self.starter_torque = 70 69 | self.starter_speed = 500 70 | self.redline = 8000 71 | self.throttle_gamma = 2.0 72 | 73 | self.node_name = "generated_engine" 74 | self.engine_name = "Test Engine" 75 | 76 | self.hf_gain = 0.01 77 | self.noise = 1.0 78 | self.jitter = 0.1 79 | 80 | self.stroke = 86 81 | self.bore = 86 82 | self.rod_length = 120 83 | self.rod_mass = 50 84 | self.compression_height = 25.4 85 | self.crank_mass = 10 86 | self.flywheel_mass = 10 87 | self.flywheel_radius = 100 88 | self.piston_mass = 50 89 | self.piston_blowby = 0.0 90 | 91 | self.plenum_volume = 1.325 92 | self.plenum_cross_section_area = 20.0 93 | self.intake_flow_rate = 3000 94 | self.runner_flow_rate = 400 95 | self.runner_length = 16 96 | self.idle_flow_rate = 0 97 | 98 | self.exhaust_length = 20 99 | 100 | self.camshaft_node_name = "generated_camshaft" 101 | self.lobe_separation = 114 102 | self.camshaft_base_radius = 0.5 103 | self.intake_lobe_center = 90 104 | self.exhaust_lobe_center = 112 105 | 106 | self.intake_lobe_lift = 551 107 | self.intake_lobe_duration = 234 108 | self.intake_lobe_gamma = 1.1 109 | self.intake_lobe_steps = 512 110 | 111 | self.exhaust_lobe_lift = 551 112 | self.exhaust_lobe_duration = 235 113 | self.exhaust_lobe_gamma = 1.1 114 | self.exhaust_lobe_steps = 512 115 | 116 | self.cylinder_head_node_name = "generated_head" 117 | self.chamber_volume = 300 118 | self.intake_runner_volume = 149.6 119 | self.intake_runner_cross_section = [1.75, 1.75] 120 | self.exhaust_runner_volume = 50.0 121 | self.exhaust_runner_cross_section = [1.75, 1.75] 122 | 123 | self.intake_flow = [0,58,103,156,214,249,268,280,280,281] 124 | self.exhaust_flow = [0,37,72,113,160,196,222,235,245,246] 125 | 126 | self.rod_journals = None 127 | self.firing_order = firing_order 128 | 129 | self.simulation_frequency = 10000 130 | self.max_sle_solver_steps = 128 131 | self.fluid_simulation_steps = 4 132 | 133 | self.idle_throttle_plate_position = 0.999 134 | self.engine_sim_version = [0, 1, 12, 2] 135 | 136 | self.timing_curve = [ 137 | [0,18], 138 | [1000,40], 139 | [2000,40], 140 | [3000,40], 141 | [4000,40], 142 | [5000,40], 143 | [6000,40], 144 | [7000,40], 145 | [8000,40], 146 | [9000,40], 147 | ] 148 | 149 | self.rev_limit = self.redline + 1000 150 | self.limiter_duration = 0.1 151 | 152 | self.vehicle = Vehicle() 153 | self.transmission = Transmission([2.8, 2.29, 1.93, 1.583, 1.375, 1.19]) 154 | 155 | def get_cylinder_bank(self, cylinder): 156 | for bank in self.banks: 157 | if cylinder in bank.cylinders: 158 | return bank 159 | 160 | return None 161 | 162 | def tdc(self): 163 | return 90 + self.banks[0].bank_angle 164 | 165 | def generate_rod_journals(self): 166 | n_cylinders = len(self.firing_order) 167 | gap = 720 / n_cylinders 168 | tdc = self.tdc() 169 | current_crank_angle = 0 170 | 171 | self.rod_journals = [0] * n_cylinders 172 | for cylinder in self.firing_order: 173 | bank = self.get_cylinder_bank(cylinder) 174 | bank_angle = bank.bank_angle + 90 175 | self.rod_journals[cylinder] = (-current_crank_angle) + bank_angle - tdc 176 | current_crank_angle -= gap 177 | 178 | 179 | def cylinder_count(self): 180 | n = 0 181 | for bank in self.banks: 182 | n += len(bank.cylinders) 183 | 184 | return n 185 | 186 | def generate_camshafts(self): 187 | n_cylinders = len(self.firing_order) 188 | gap = 720 / n_cylinders 189 | for cylinder in self.firing_order: 190 | bank = self.get_cylinder_bank(cylinder) 191 | 192 | firing_order_position = self.firing_order.index(cylinder) 193 | lobe_index = bank.get_cylinder_index(cylinder) 194 | 195 | bank.camshaft.lobes[lobe_index] = firing_order_position * gap 196 | 197 | def generate(self): 198 | self.generate_rod_journals() 199 | self.generate_camshafts() 200 | 201 | def write_head(self, file): 202 | file.write("""private node {} {{ 203 | input intake_camshaft; 204 | input exhaust_camshaft; 205 | input chamber_volume: {} * units.cc; 206 | input intake_runner_volume: {} * units.cc; 207 | input intake_runner_cross_section_area: {} * units.inch * {} * units.inch; 208 | input exhaust_runner_volume: {} * units.cc; 209 | input exhaust_runner_cross_section_area: {} * units.inch * {} * units.inch; 210 | 211 | input flow_attenuation: 1.0; 212 | input lift_scale: 1.0; 213 | input flip_display: false; 214 | alias output __out: head; 215 | 216 | function intake_flow(50 * units.thou) 217 | intake_flow""".format(self.cylinder_head_node_name, 218 | self.chamber_volume, 219 | self.intake_runner_volume, 220 | self.intake_runner_cross_section[0], 221 | self.intake_runner_cross_section[1], 222 | self.exhaust_runner_volume, 223 | self.exhaust_runner_cross_section[0], 224 | self.exhaust_runner_cross_section[1] 225 | )) 226 | 227 | for i in range(len(self.intake_flow)): 228 | file.write("\n") 229 | file.write(" .add_flow_sample({} * lift_scale, {} * flow_attenuation)".format(i * 50, self.intake_flow[i])) 230 | 231 | file.write("""\n\n function exhaust_flow(50 * units.thou) 232 | exhaust_flow""") 233 | 234 | for i in range(len(self.exhaust_flow)): 235 | file.write("\n") 236 | file.write(" .add_flow_sample({} * lift_scale, {} * flow_attenuation)".format(i * 50, self.exhaust_flow[i])) 237 | 238 | file.write("""\n\n generic_cylinder_head head( 239 | chamber_volume: chamber_volume, 240 | intake_runner_volume: intake_runner_volume, 241 | intake_runner_cross_section_area: intake_runner_cross_section_area, 242 | exhaust_runner_volume: exhaust_runner_volume, 243 | exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, 244 | 245 | intake_port_flow: intake_flow, 246 | exhaust_port_flow: exhaust_flow, 247 | valvetrain: standard_valvetrain( 248 | intake_camshaft: intake_camshaft, 249 | exhaust_camshaft: exhaust_camshaft 250 | ), 251 | flip_display: flip_display 252 | ) 253 | }\n 254 | """) 255 | 256 | def write_camshaft(self, file): 257 | file.write("""private node {} {{ 258 | input lobe_profile; 259 | input intake_lobe_profile: lobe_profile; 260 | input exhaust_lobe_profile: lobe_profile; 261 | input lobe_separation: {} * units.deg; 262 | input intake_lobe_center: lobe_separation; 263 | input exhaust_lobe_center: lobe_separation; 264 | input advance: 0 * units.deg; 265 | input base_radius: {} * units.inch;""".format(self.camshaft_node_name, self.lobe_separation, self.camshaft_base_radius)) 266 | 267 | for index, bank in enumerate(self.banks): 268 | file.write("\n") 269 | file.write(" output intake_cam_{}: _intake_cam_{};\n".format(index, index)) 270 | file.write(" output exhaust_cam_{}: _exhaust_cam_{};\n".format(index, index)) 271 | 272 | file.write( 273 | """ camshaft_parameters params ( 274 | advance: advance, 275 | base_radius: base_radius 276 | ) 277 | """) 278 | 279 | for index, bank in enumerate(self.banks): 280 | file.write("\n") 281 | file.write(" camshaft _intake_cam_{}(params, lobe_profile: intake_lobe_profile)\n".format(index)) 282 | file.write(" camshaft _exhaust_cam_{}(params, lobe_profile: exhaust_lobe_profile)\n".format(index)) 283 | 284 | file.write(" label rot360(360 * units.deg)\n") 285 | 286 | for index, bank in enumerate(self.banks): 287 | file.write(" _exhaust_cam_{}\n".format(index)) 288 | for lobe in bank.camshaft.lobes: 289 | file.write(" .add_lobe(rot360 - exhaust_lobe_center + {} * units.deg)\n".format(lobe)) 290 | 291 | file.write(" _intake_cam_{}\n".format(index)) 292 | for lobe in bank.camshaft.lobes: 293 | file.write(" .add_lobe(rot360 + exhaust_lobe_center + {} * units.deg)\n".format(lobe)) 294 | 295 | file.write("}\n") 296 | 297 | def write_engine(self, file): 298 | file.write("""\npublic node {} {{ 299 | alias output __out: engine; 300 | 301 | """.format(self.node_name)) 302 | 303 | file.write(""" engine engine( 304 | name: "{}", 305 | starter_torque: {} * units.lb_ft, 306 | starter_speed: {} * units.rpm, 307 | redline: {} * units.rpm, 308 | throttle_gamma: {}, 309 | fuel: fuel( 310 | {} 311 | ), 312 | hf_gain: {}, 313 | noise: {}, 314 | jitter: {}, 315 | simulation_frequency: {} 316 | """.format(self.engine_name, self.starter_torque, self.starter_speed, self.redline, self.throttle_gamma, self.fuel.generate(), self.hf_gain, self.noise, self.jitter, self.simulation_frequency)) 317 | 318 | if self.engine_sim_version[2] >= 13: 319 | file.write(""", 320 | fluid_simulation_steps: {}, 321 | max_sle_solver_steps: {} 322 | """, self.max_sle_solver_steps, self.fluid_simulation_steps) 323 | 324 | file.write(")\n\n wires wires()\n") 325 | 326 | file.write(""" 327 | label stroke({} * units.mm) 328 | label bore({} * units.mm) 329 | label rod_length({} * units.mm) 330 | label rod_mass({} * units.g) 331 | label compression_height({} * units.mm) 332 | label crank_mass({} * units.kg) 333 | label flywheel_mass({} * units.kg) 334 | label flywheel_radius({} * units.mm) 335 | 336 | label crank_moment( 337 | disk_moment_of_inertia(mass: crank_mass, radius: stroke) 338 | ) 339 | label flywheel_moment( 340 | disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) 341 | ) 342 | label other_moment( // Moment from cams, pulleys, etc [estimated] 343 | disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) 344 | )""".format(self.stroke, self.bore, self.rod_length, self.rod_mass, self.compression_height, self.crank_mass, self.flywheel_mass, self.flywheel_radius)) 345 | 346 | file.write("""\n\n crankshaft c0( 347 | throw: stroke / 2, 348 | flywheel_mass: flywheel_mass, 349 | mass: crank_mass, 350 | friction_torque: 1.0 * units.lb_ft, 351 | moment_of_inertia: 352 | crank_moment + flywheel_moment + other_moment, 353 | position_x: 0.0, 354 | position_y: 0.0, 355 | tdc: {} * units.deg 356 | )\n""".format(self.tdc())) 357 | 358 | file.write("\n") 359 | for index, journal in enumerate(self.rod_journals): 360 | file.write(" rod_journal rj{}(angle: {} * units.deg)\n".format(index, journal)) 361 | 362 | file.write(" c0\n") 363 | for index, journal in enumerate(self.rod_journals): 364 | file.write(" .add_rod_journal(rj{})\n".format(index)) 365 | 366 | file.write("\n") 367 | 368 | file.write(""" piston_parameters piston_params( 369 | mass: ({}) * units.g, // 414 - piston mass, 152 - pin weight 370 | compression_height: compression_height, 371 | wrist_pin_position: 0.0, 372 | displacement: 0.0 373 | )\n\n""".format(self.piston_mass)) 374 | 375 | file.write(""" connecting_rod_parameters cr_params( 376 | mass: rod_mass, 377 | moment_of_inertia: rod_moment_of_inertia( 378 | mass: rod_mass, 379 | length: rod_length 380 | ), 381 | center_of_mass: 0.0, 382 | length: rod_length 383 | )\n""") 384 | 385 | file.write(""" intake intake( 386 | plenum_volume: {} * units.L, 387 | plenum_cross_section_area: {} * units.cm2, 388 | intake_flow_rate: k_carb({}), 389 | runner_flow_rate: k_carb({}), 390 | runner_length: {} * units.inch, 391 | idle_flow_rate: k_carb({}), 392 | idle_throttle_plate_position: {}, 393 | velocity_decay: 0.5 394 | )\n""".format(self.plenum_volume, self.plenum_cross_section_area, self.intake_flow_rate, self.runner_flow_rate, self.runner_length, self.idle_flow_rate, self.idle_throttle_plate_position)) 395 | 396 | file.write(""" exhaust_system_parameters es_params( 397 | outlet_flow_rate: k_carb(2000.0), 398 | primary_tube_length: 20.0 * units.inch, 399 | primary_flow_rate: k_carb(200.0), 400 | velocity_decay: 0.5 401 | )\n""") 402 | 403 | for index, bank in enumerate(self.banks): 404 | file.write(""" exhaust_system exhaust{}( 405 | es_params, 406 | audio_volume: 1.0 * 0.004, 407 | length: {} * units.inch, 408 | impulse_response: ir_lib.minimal_muffling_01 409 | )\n\n""".format(index, self.exhaust_length)) 410 | 411 | file.write(""" cylinder_bank_parameters bank_params( 412 | bore: bore, 413 | deck_height: stroke / 2 + rod_length + compression_height 414 | )\n\n""") 415 | 416 | file.write(" label spacing(0.0)\n") 417 | 418 | for index, bank in enumerate(self.banks): 419 | file.write(" cylinder_bank b{}(bank_params, angle: {} * units.deg)\n".format(index, bank.bank_angle)) 420 | 421 | for index, bank in enumerate(self.banks): 422 | file.write(" b{}\n".format(index)) 423 | for cylinder_index, cylinder in enumerate(bank.cylinders): 424 | file.write(""" .add_cylinder( 425 | piston: piston(piston_params, blowby: k_28inH2O({})), 426 | connecting_rod: connecting_rod(cr_params), 427 | rod_journal: rj{}, 428 | intake: intake, 429 | exhaust_system: exhaust{}, 430 | ignition_wire: wires.wire{}, 431 | sound_attenuation: {}, 432 | primary_length: {} * spacing * 0.5 * units.cm 433 | )\n""".format(self.piston_blowby, cylinder, index, cylinder, random.uniform(0.5, 1.0), cylinder_index)) 434 | 435 | file.write(""" .set_cylinder_head( 436 | {}( 437 | intake_camshaft: camshaft.intake_cam_{}, 438 | exhaust_camshaft: camshaft.exhaust_cam_{}, 439 | flip_display: {}, 440 | flow_attenuation: 1.0) 441 | )\n\n""".format(self.cylinder_head_node_name, index, index, "true" if bank.flip else "false")) 442 | 443 | file.write(" engine\n") 444 | for index, bank in enumerate(self.banks): 445 | file.write(" .add_cylinder_bank(b{})\n".format(index)) 446 | file.write("\n") 447 | 448 | file.write(" engine.add_crankshaft(c0)\n\n") 449 | 450 | file.write(""" harmonic_cam_lobe intake_lobe( 451 | duration_at_50_thou: {} * units.deg, 452 | gamma: {}, 453 | lift: {} * units.thou, 454 | steps: {} 455 | ) 456 | 457 | harmonic_cam_lobe exhaust_lobe( 458 | duration_at_50_thou: {} * units.deg, 459 | gamma: {}, 460 | lift: {} * units.thou, 461 | steps: {} 462 | )\n\n""".format(self.intake_lobe_duration, self.intake_lobe_gamma, self.intake_lobe_lift, self.intake_lobe_steps, self.exhaust_lobe_duration, self.exhaust_lobe_gamma, self.exhaust_lobe_lift, self.exhaust_lobe_steps)) 463 | 464 | file.write(""" {} camshaft( 465 | lobe_profile: "N/A", 466 | 467 | intake_lobe_profile: intake_lobe, 468 | exhaust_lobe_profile: exhaust_lobe, 469 | intake_lobe_center: {} * units.deg, 470 | exhaust_lobe_center: {} * units.deg 471 | )\n\n""".format(self.camshaft_node_name, self.intake_lobe_center, self.exhaust_lobe_center)) 472 | 473 | file.write(""" function timing_curve(1000 * units.rpm) 474 | timing_curve""") 475 | for point in self.timing_curve: 476 | file.write("\n") 477 | file.write(" .add_sample({} * units.rpm, {} * units.deg)".format(point[0], point[1])) 478 | 479 | file.write("\n\n") 480 | 481 | file.write(""" ignition_module ignition_module( 482 | timing_curve: timing_curve, 483 | rev_limit: {} * units.rpm, 484 | limiter_duration: 0.1)\n\n""".format(self.rev_limit, self.limiter_duration)) 485 | 486 | file.write(" ignition_module\n") 487 | for index, cylinder in enumerate(self.firing_order): 488 | file.write(" .connect_wire(wires.wire{}, {} * units.deg)\n".format(cylinder, 720 * (index / len(self.firing_order)))) 489 | 490 | file.write("\n engine.add_ignition_module(ignition_module)\n") 491 | 492 | file.write("}\n\n") 493 | 494 | def write_vehicle_transmission(self, file): 495 | file.write("""private node {} {{ 496 | alias output __out: 497 | vehicle( 498 | mass: {} * units.kg, 499 | drag_coefficient: {}, 500 | cross_sectional_area: ({} * units.inch) * ({} * units.inch), 501 | diff_ratio: {}, 502 | tire_radius: {} * units.inch, 503 | rolling_resistance: {} * units.N 504 | ); 505 | }}\n\n""".format( 506 | self.vehicle.node_name, 507 | self.vehicle.mass, 508 | self.vehicle.drag_coefficient, 509 | self.vehicle.cross_sectional_area[0], 510 | self.vehicle.cross_sectional_area[1], 511 | self.vehicle.diff_ratio, 512 | self.vehicle.tire_radius, 513 | self.vehicle.rolling_resistance 514 | )) 515 | 516 | file.write("""private node {} {{ 517 | alias output __out: 518 | transmission( 519 | max_clutch_torque: {} * units.lb_ft 520 | )""".format(self.transmission.node_name, self.transmission.max_clutch_torque)) 521 | 522 | for gear in self.transmission.gears: 523 | file.write("\n") 524 | file.write(" .add_gear({})".format(gear)) 525 | 526 | file.write(";\n}\n\n") 527 | 528 | def write_main_node(self, file): 529 | file.write("""public node main {{ 530 | run( 531 | engine: {}(), 532 | vehicle: {}(), 533 | transmission: {}() 534 | ) 535 | }} 536 | 537 | main()\n""".format(self.node_name, self.vehicle.node_name, self.transmission.node_name)) 538 | 539 | def __write(self, file): 540 | file.write( 541 | """import "engine_sim.mr" 542 | 543 | units units() 544 | constants constants() 545 | impulse_response_library ir_lib() 546 | 547 | """) 548 | 549 | file.write("private node wires {\n") 550 | for cylinder in range(self.cylinder_count()): 551 | file.write(" output wire{}: ignition_wire();\n".format(cylinder)) 552 | file.write("}\n\n") 553 | 554 | self.write_head(file) 555 | self.write_camshaft(file) 556 | self.write_engine(file) 557 | self.write_vehicle_transmission(file) 558 | self.write_main_node(file) 559 | 560 | def write_to_string(self): 561 | file = io.StringIO() 562 | self.__write(file) 563 | return file.getvalue() 564 | 565 | def write_to_console(self): 566 | print(self.write_to_string()) 567 | 568 | def write_to_file(self, fname): 569 | with open(fname, 'w') as file: 570 | self.__write(file) 571 | --------------------------------------------------------------------------------