Skip to content

Instantly share code, notes, and snippets.

@fwilleke80
Last active July 17, 2019 11:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fwilleke80/3d8cd26490802a6d267fa2a86a73f643 to your computer and use it in GitHub Desktop.
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.
"""
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