Last active
July 17, 2019 11:12
-
-
Save fwilleke80/3d8cd26490802a6d267fa2a86a73f643 to your computer and use it in GitHub Desktop.
[C4D] A nicely customisable Python script that creates complex random scene hierarchies in Cinema 4D. Good for performance testing in cases where number of objects is an issue.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Name-US:Create random scene... | |
Description-US:Create random scene (hold SHIFT to use previous settings, hold CTRL to forget previous settings) | |
""" | |
import sys | |
import time | |
import json | |
import random | |
import c4d | |
SCRIPT_TITLE = 'Create Random Scene' | |
SCRIPT_VERSION = '1.3' | |
default_parameters = { | |
"random_seed": 0, # Random seed. Use 0 for a different seed every time the script is called. | |
"max_depth": 5, # Maximum hierarchy recursion depth | |
"max_objects_per_level": 9, # Maximum number of object per hierarchy level | |
"max_objects_total": 5000, # Hard limit for object count | |
"recurse_threshold": 0.2, # 0.0 <= resurce_threshold <= 1.0; larger values lead to more children | |
"distance": 10.0, # Children are moved a maximum of (child_object_size * (distance / (current_depth + 1))) away from their parents | |
"max_rotation": 15.0, # Maximum rotation around any axis (degrees) | |
"add_phong": 1, # Add Phong tag or not? | |
"object_types": # List of allowed object types | |
[ | |
c4d.Ocube, | |
c4d.Osphere, | |
c4d.Opyramid, | |
c4d.Otorus, | |
c4d.Ocylinder, | |
c4d.Otube, | |
c4d.Ocapsule, | |
c4d.Ooiltank, | |
c4d.Oplatonic | |
] | |
} | |
def get_default_settings(): | |
"""Get default settings stored in WorldContainer. | |
If not settings stored, use default_parameters | |
""" | |
settings = default_parameters | |
worldContainer = c4d.GetWorldContainerInstance() | |
myContainer = worldContainer.GetContainer(1053066) | |
if myContainer is not None: | |
settingsString = myContainer.GetString(10001) | |
if settingsString: | |
settings = json.loads(settingsString) | |
return settings | |
def set_default_settings(settings): | |
"""Store settings in WorldContainer | |
""" | |
worldContainer = c4d.GetWorldContainerInstance() | |
myContainer = c4d.BaseContainer() | |
settingsString = json.dumps(settings) | |
myContainer.SetString(10001, settingsString) | |
worldContainer.SetContainer(1053066, myContainer) | |
class SettingsDialog(c4d.gui.GeDialog): | |
def __init__(self): | |
"""Initialize class instance | |
""" | |
# Gadget IDs | |
self.ID_SEED = 10001 | |
self.ID_MAX_DEPTH = 10002 | |
self.ID_MAX_OBJECTS_PER_LEVEL = 10003 | |
self.ID_MAX_OBJECTS_TOTAL = 10004 | |
self.ID_RECURSE_THRESHOLD = 10005 | |
self.ID_DISTANCE = 10006 | |
self.ID_MAX_ROTATION = 10007 | |
self.ID_ADD_PHONG = 10008 | |
# Label strings | |
self.IDS_DIALOGTITLE = SCRIPT_TITLE + ' ' + SCRIPT_VERSION | |
self.IDS_GROUPTITLE = 'Settings' | |
self.IDS_SEED = "Random Seed" | |
self.IDS_MAX_DEPTH = "Max. Depth" | |
self.IDS_MAX_OBJECTS_PER_LEVEL = "Max. Objects per Level" | |
self.IDS_MAX_OBJECTS_TOTAL = "Max. Objects Total" | |
self.IDS_RECURSE_THRESHOLD = "Recurse Threshold" | |
self.IDS_DISTANCE = "Distance" | |
self.IDS_MAX_ROTATION = "Max. Rotation" | |
self.IDS_ADD_PHONG = "Add Phong Tag" | |
# Value storage | |
self.settings = get_default_settings() | |
def CreateLayout(self): | |
"""Construct dialog layout | |
""" | |
# Main dialog | |
self.SetTitle(title=self.IDS_DIALOGTITLE) | |
# Settings | |
self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, | |
cols=2, title=self.IDS_GROUPTITLE) | |
self.GroupBorder(border=c4d.BORDER_GROUP_IN) | |
self.GroupSpace(spacex=5, spacey=5) | |
self.GroupBorderSpace(left=5, right=5, top=5, bottom=5) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, | |
initw=150, name=self.IDS_SEED) | |
self.AddEditNumberArrows( | |
id=self.ID_SEED, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, | |
initw=150, name=self.IDS_MAX_DEPTH) | |
self.AddEditNumberArrows(id=self.ID_MAX_DEPTH, | |
flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, initw=150, | |
name=self.IDS_MAX_OBJECTS_PER_LEVEL) | |
self.AddEditNumberArrows( | |
id=self.ID_MAX_OBJECTS_PER_LEVEL, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, initw=150, | |
name=self.IDS_MAX_OBJECTS_TOTAL) | |
self.AddEditNumberArrows( | |
id=self.ID_MAX_OBJECTS_TOTAL, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, initw=150, | |
name=self.IDS_RECURSE_THRESHOLD) | |
self.AddEditNumberArrows( | |
id=self.ID_RECURSE_THRESHOLD, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, | |
initw=150, name=self.IDS_DISTANCE) | |
self.AddEditNumberArrows( | |
id=self.ID_DISTANCE, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddStaticText(id=0, flags=c4d.BFH_LEFT, | |
initw=150, name=self.IDS_MAX_ROTATION) | |
self.AddEditNumberArrows( | |
id=self.ID_MAX_ROTATION, flags=c4d.BFH_RIGHT, initw=80) | |
self.AddCheckbox(id=self.ID_ADD_PHONG, flags=c4d.BFH_LEFT, | |
initw=200, inith=0, name=self.IDS_ADD_PHONG) | |
self.GroupEnd() | |
# Buttons | |
self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL) | |
return True | |
def InitValues(self): | |
self.SetInt32(id=self.ID_SEED, | |
value=self.settings['random_seed'], min=0) | |
self.SetInt32(id=self.ID_MAX_DEPTH, | |
value=self.settings['max_depth'], min=0) | |
self.SetInt32(id=self.ID_MAX_OBJECTS_PER_LEVEL, | |
value=self.settings['max_objects_per_level'], min=0) | |
self.SetInt32(id=self.ID_MAX_OBJECTS_TOTAL, | |
value=self.settings['max_objects_total'], min=0) | |
self.SetFloat(id=self.ID_RECURSE_THRESHOLD, | |
value=self.settings['recurse_threshold'], min=0.0, max=1.0, step=0.01) | |
self.SetMeter(id=self.ID_DISTANCE, | |
value=self.settings['distance'], min=0.0, step=0.1) | |
self.SetDegree(id=self.ID_MAX_ROTATION, value=c4d.utils.DegToRad( | |
self.settings['max_rotation']), step=0.1) | |
self.SetBool(id=self.ID_ADD_PHONG, value=self.settings['add_phong']) | |
return True | |
def Command(self, id, msg): | |
# OK button | |
if id == c4d.DLG_OK: | |
self.settings['do_it'] = True | |
self.Close() | |
# Cancel button | |
elif id == c4d.DLG_CANCEL: | |
self.settings['do_it'] = False | |
self.Close() | |
# Parameters | |
elif id == self.ID_SEED: | |
self.settings['random_seed'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_MAX_DEPTH: | |
self.settings['max_depth'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_MAX_OBJECTS_PER_LEVEL: | |
self.settings['max_objects_per_level'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_MAX_OBJECTS_TOTAL: | |
self.settings['max_objects_total'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_RECURSE_THRESHOLD: | |
self.settings['recurse_threshold'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_DISTANCE: | |
self.settings['distance'] = msg[c4d.BFM_ACTION_VALUE] | |
elif id == self.ID_MAX_ROTATION: | |
print c4d.utils.RadToDeg(msg[c4d.BFM_ACTION_VALUE]) | |
self.settings['max_rotation'] = c4d.utils.RadToDeg(msg[c4d.BFM_ACTION_VALUE]) | |
elif id == self.ID_ADD_PHONG: | |
self.settings['add_phong'] = msg[c4d.BFM_ACTION_VALUE] | |
return True | |
# Global object counter | |
totalObjectCount = 0 | |
def random_vector(): | |
"""Return a random c4d.Vector() with component values ranging from -1.0 to 1.0 | |
""" | |
return c4d.Vector(random.random() * 2.0 - 1.0, random.random() * 2.0 - 1.0, random.random() * 2.0 - 1.0) | |
def create_random_object(objectTypes): | |
"""Create an object of random type chosen from parameters['object_types'] | |
""" | |
return c4d.BaseObject(random.choice(objectTypes)) | |
def create_random_children(maxObjects, maxGlobalObjects, parentObject, objectTypes, addPhong): | |
"""Create a maximum of maxObjects of randomly typed objects unter parentObject | |
""" | |
# Check if we want more objects created | |
global totalObjectCount | |
if totalObjectCount >= maxGlobalObjects: | |
return 0 | |
# Compute how many objects will be created | |
numObjects = min(random.randint(1, maxObjects), | |
maxGlobalObjects - totalObjectCount) | |
totalObjectCount += numObjects | |
# Create objects | |
if numObjects > 0: | |
for i in range(numObjects): | |
newObject = create_random_object(objectTypes) | |
if newObject is not None: | |
if addPhong: | |
phongTag = newObject.MakeTag(c4d.Tphong) | |
if phongTag is not None: | |
phongTag[c4d.PHONGTAG_PHONG_ANGLELIMIT] = True | |
newObject.InsertUnder(parentObject) | |
return numObjects | |
def create_scene_level(parentObject, max_depth, maxObjectsPerLevel, maxGlobalObjects, objectTypes, addPhong, recurseThreshold, distance, maxRotation, curr_depth=0): | |
"""Create a number of children under parentObject, then iterate those children, move them, and recurse to create children's children | |
""" | |
# Create children | |
objectCount = create_random_children( | |
maxObjectsPerLevel, maxGlobalObjects, parentObject, objectTypes, addPhong) | |
# Memorize parent position | |
parentMg = parentObject.GetMg() | |
# Iterate children | |
iterObject = parentObject.GetDown() | |
while iterObject: | |
# Build object matrix | |
iterObjectMg = c4d.Matrix() | |
# Position | |
objectSize = iterObject.GetRad() * 2.0 | |
randomOffsetVector = random_vector().GetNormalized() | |
randomOffsetVector.x *= objectSize.x | |
randomOffsetVector.y *= objectSize.y | |
randomOffsetVector.z *= objectSize.z | |
iterObjectMg.off = randomOffsetVector * distance | |
# Rotation | |
if maxRotation > 0.0: | |
randomRotation = random_vector().GetNormalized() | |
randomRotation *= maxRotation | |
rotMatrix = c4d.utils.HPBToMatrix(randomRotation) | |
iterObjectMg = rotMatrix * iterObjectMg | |
# Set object matrix | |
iterObject.SetMg(parentMg * iterObjectMg) | |
# Recurse to create children | |
rndVal = random.random() | |
if curr_depth < max_depth and rndVal > recurseThreshold: | |
objectCount += create_scene_level(iterObject, max_depth, maxObjectsPerLevel, maxGlobalObjects, objectTypes, | |
addPhong, recurseThreshold, distance / float(curr_depth + 1), maxRotation, curr_depth + 1) | |
iterObject = iterObject.GetNext() | |
return objectCount | |
def main(): | |
# Detect SHIFT and CTRL key | |
force = False | |
res = c4d.BaseContainer() | |
if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_QUALIFIER, res): | |
skipDialog = res[c4d.BFM_INPUT_QUALIFIER] & c4d.QSHIFT | |
resetDefaults = res[c4d.BFM_INPUT_QUALIFIER] & c4d.QCTRL | |
# Reset defaults? | |
if resetDefaults and c4d.gui.QuestionDialog('Do you want to revert stored settings to default?'): | |
set_default_settings(default_parameters) | |
# Open dialog? | |
if not skipDialog: | |
theDialog = SettingsDialog() | |
theDialog.Open(dlgtype=c4d.DLG_TYPE_MODAL) | |
if not theDialog.settings['do_it']: | |
return | |
parameters = theDialog.settings | |
parameters.pop('do_it') | |
set_default_settings(parameters) | |
else: | |
parameters = get_default_settings() | |
# Initialize random generator | |
randomSeed = parameters['random_seed'] | |
if randomSeed != 0: | |
random.seed(randomSeed) | |
else: | |
random.seed(time.time()) | |
# Create parent group Null | |
global totalObjectCount | |
parentNull = c4d.BaseObject(c4d.Onull) | |
if parentNull is None: | |
sys.exit('Could not create parent Null object!') | |
parentNull.SetName('Random scene') | |
totalObjectCount = 1 | |
# Start undo | |
doc.StartUndo() | |
# Build hierarchy of new objects | |
create_scene_level(parentNull, | |
parameters['max_depth'], | |
parameters['max_objects_per_level'], | |
parameters['max_objects_total'], | |
parameters['object_types'], | |
parameters['add_phong'], | |
parameters['recurse_threshold'], | |
parameters['distance'], | |
c4d.utils.DegToRad(parameters['max_rotation'])) | |
print('Created ' + str(totalObjectCount) + ' new objects!') | |
# Insert parent group into document | |
doc.InsertObject(parentNull, parent=doc.GetActiveObject()) | |
doc.Message(c4d.MSG_UPDATE) | |
doc.AddUndo(c4d.UNDOTYPE_NEW, parentNull) | |
# Finish undo | |
doc.EndUndo() | |
# Refresh viewport | |
c4d.EventAdd() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment