diff --git a/i_scene_cp77_gltf/importers/entity_import.py b/i_scene_cp77_gltf/importers/entity_import.py index 77a9a54..5999556 100644 --- a/i_scene_cp77_gltf/importers/entity_import.py +++ b/i_scene_cp77_gltf/importers/entity_import.py @@ -221,7 +221,6 @@ def importEnt(with_materials, filepath='', appearances=[], exclude_meshes=[], in app_name=a['appearanceName']['$value'] if ent_app_idx<0 and app_name =='default': - #ent_default=j['Data']['RootChunk']['defaultAppearance']['$value'] for i,a in enumerate(ent_apps): if a['name']['$value']==ent_default: print('appearance matched, id = ',i) diff --git a/i_scene_cp77_gltf/importers/import_with_materials.py b/i_scene_cp77_gltf/importers/import_with_materials.py index 85ee341..6630476 100644 --- a/i_scene_cp77_gltf/importers/import_with_materials.py +++ b/i_scene_cp77_gltf/importers/import_with_materials.py @@ -46,6 +46,7 @@ def CP77GLBimport(self, with_materials, remap_depot, exclude_unused_mats=True, i # obj = None start_time = time.time() loadfiles=self.files + DepotPath=cp77_addon_prefs appearances=self.appearances.split(",") if not cp77_addon_prefs.non_verbose: if ".anims.glb" in self.filepath: @@ -138,6 +139,7 @@ def CP77GLBimport(self, with_materials, remap_depot, exclude_unused_mats=True, i #Kwek: Gate this--do the block iff corresponding Material.json exist #Kwek: was tempted to do a try-catch, but that is just La-Z #Kwek: Added another gate for materials + DepotPath=None blender_4_scale_armature_bones() if ".anims.glb" in filepath: break @@ -145,8 +147,9 @@ def CP77GLBimport(self, with_materials, remap_depot, exclude_unused_mats=True, i if with_materials==True and has_material_json: matjsonpath = current_file_base_path + ".Material.json" DepotPath, json_apps, mats = jsonload(matjsonpath) - if DepotPath == None: - break + if DepotPath == None: + break + #DepotPath = str(obj["MaterialRepo"]) + "\\" context=bpy.context if remap_depot and os.path.exists(context.preferences.addons[__name__.split('.')[0]].preferences.depotfolder_path): @@ -182,6 +185,7 @@ def CP77GLBimport(self, with_materials, remap_depot, exclude_unused_mats=True, i if import_garmentsupport: manage_garment_support(existingMeshes, gltf_importer) + if not cp77_addon_prefs.non_verbose: print(f"GLB Import Time: {(time.time() - start_time)} Seconds") diff --git a/i_scene_cp77_gltf/importers/sector_import.py b/i_scene_cp77_gltf/importers/sector_import.py index cb25c6e..8319bde 100644 --- a/i_scene_cp77_gltf/importers/sector_import.py +++ b/i_scene_cp77_gltf/importers/sector_import.py @@ -33,6 +33,18 @@ VERBOSE=True scale_factor=1 +def find_debugName(obj): + debugName=None + if 'debugName' in obj.users_collection[0].keys(): + debugName=obj.users_collection[0]['debugName'] + else: + if 'debugName' in D.collections[coll_parents.get(obj.users_collection[0].name)]: + debugName=D.collections[coll_parents.get(obj.users_collection[0].name)]['debugName'] + else: + if 'debugName' in D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))]: + debugName=D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))]['debugName'] + return debugName + def points_within_tol(point1, point2, tolerance=0.01): """ Check if two points are within a specified tolerance. @@ -251,6 +263,17 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding print('-------------------- Importing Cyberpunk 2077 Streaming Sectors --------------------') print('') start_time = time.time() + # Set this to true to limit import to the types listed in the import_types list. + limittypes=False + import_types=None + import_types=['worldStaticSoundEmitterNode', + 'worldStaticParticleNode', + 'worldEffectNode', + 'worldPopulationSpawnerNode', + 'worldClothMeshNode', + 'worldRotatingMeshNode', + 'worldCollisionNode' + ] # Enter the path to your projects source\raw\base folder below, needs double slashes between folder names. path = os.path.join( os.path.dirname(filepath),'source','raw','base') print('path is ',path) @@ -269,8 +292,9 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding jsonpath = glob.glob(os.path.join(path, "**", "*.streamingsector.json"), recursive = True) meshes=[] C = bpy.context + I_want_to_break_free=False # Use object wireframe colors not theme - doesnt work need to find hte viewport as the context doesnt return that for this call - # bpy.context.space_data.shading.wireframe_color_type = 'OBJECT' + # bpy.context.space_data.shading.wireframe_color_type = 'OBJECT' for filepath in jsonpath: if filepath==os.path.join(path,os.path.basename(project)+'.streamingsector.json'): continue @@ -280,11 +304,14 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding sectorName=os.path.basename(filepath)[:-5] #print(len(nodes)) #nodes=[] + for i,e in enumerate(nodes): print(i) data = e['Data'] type = data['$type'] - if True: # type=='worldBendedMeshNode' :#or type=='worldCableMeshNode': # can add a filter for dev here + if I_want_to_break_free: + break + if (limittypes and type in import_types) or limittypes==False :#or type=='worldCableMeshNode': # can add a filter for dev here match type: case 'worldEntityNode'|'worldDeviceNode': #print('worldEntityNode',i) @@ -426,7 +453,7 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding # continue data = e['Data'] type = data['$type'] - if True:#type=='worldBendedMeshNode' :#or type=='worldCableMeshNode': # can add a filter for dev here + if (limittypes and type in import_types) or limittypes==False: #or type=='worldCableMeshNode': # can add a filter for dev here match type: case 'worldEntityNode' | 'worldDeviceNode': #print('worldEntityNode',i) @@ -466,15 +493,20 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding new['debugName']=e['Data']['debugName']['$value'] new['sectorName']=sectorName new['HandleId']=e['HandleId'] - new['entityTemplate']=os.path.basename(data['entityTemplate']['DepotPath']['$value']) - new['appearanceName']=data['appearanceName'] + new['entityTemplate']=data['entityTemplate']['DepotPath']['$value'] new['pivot']=inst['Pivot'] + if 'appearanceName' in data.keys(): + new['appearanceName']=data['appearanceName']['$value'] + else: + new['appearanceName']='' pos = Vector(get_pos(inst)) rot=[0,0,0,0] scale =Vector((1/scale_factor,1/scale_factor,1/scale_factor)) rot =Quaternion(get_rot(inst)) + new['ent_rot']=rot.to_euler('XYZ') + new['ent_pos']=pos inst_trans_mat=Matrix.LocRotScale(pos,rot,scale) for child in group.children: newchild=bpy.data.collections.new(child.name) @@ -625,6 +657,10 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding NDI_Coll['debugName']=e['Data']['debugName']['$value'] NDI_Coll['sectorName']=sectorName NDI_Coll['numElements']=num + if 'appearanceName' in e['Data'].keys(): + NDI_Coll['appearanceName']=e['Data']['appearanceName']['$value'] + else : + NDI_Coll['appearanceName']='' for El_idx in range(start, start+num): #create the linked copy of the group of mesh new_groupname = 'NDI'+str(inst['nodeDataIndex'])+'_'+str(El_idx)+'_'+groupname @@ -639,6 +675,10 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding new['mesh']=meshname new['debugName']=e['Data']['debugName']['$value'] new['sectorName']=sectorName + if 'appearanceName' in e['Data'].keys(): + new['appearanceName']=e['Data']['appearanceName']['$value'] + else : + new['appearanceName']='' for old_obj in group.all_objects: obj=old_obj.copy() new.objects.link(obj) @@ -772,6 +812,14 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding o['decal']=e['Data']['material']['DepotPath']['$value'] o['debugName']=e['Data']['debugName']['$value'] o['sectorName']=sectorName + o['horizontalFlip']=e['Data']['horizontalFlip'] + o['verticalFlip']=e['Data']['verticalFlip'] + o['alpha']=e['Data']['alpha'] + if 'appearanceName' in e['Data'].keys(): + o['appearanceName']=e['Data']['appearanceName']['$value'] + else : + o['appearanceName']='' + Sector_coll.objects.link(o) o.location = get_pos(inst) o.rotation_mode = "QUATERNION" @@ -926,9 +974,9 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding if type=='worldRotatingMeshNode': rot_axis=data['rotationAxis'] axis_no=0 - if rot_axis=='Z': + if rot_axis=='Y': axis_no=1 - elif rot_axis=='Y': #y & z are swapped + elif rot_axis=='Z': #y & z are swapped sometimes, need to work out why axis_no=2 rot_time=data['fullRotationTime'] @@ -947,7 +995,14 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding new['sectorName']=sectorName new['pivot']=inst['Pivot'] new['meshAppearance']=meshAppearance - + new['appearanceName']=meshAppearance + if type=='worldClothMeshNode': + new['windImpulseEnabled']= inst['windImpulseEnabled'] + if type=='worldRotatingMeshNode': + new['rot_axis']=data['rotationAxis'] + new['reverseDirection']=data['reverseDirection'] + new['fullRotationTime']=data['fullRotationTime'] + #print(new['nodeDataIndex']) # Should do something with the Advertisements lightData bits here @@ -1005,6 +1060,10 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding NDI_Coll['debugName']=e['Data']['debugName']['$value'] NDI_Coll['sectorName']=sectorName NDI_Coll['numElements']=num + if 'appearanceName' in e['Data'].keys(): + NDI_Coll['appearanceName']=e['Data']['appearanceName']['$value'] + else : + NDI_Coll['appearanceName']='' #print('Glb found - ',glbfoundname) #print('Glb found, looking for instances of ',i) instances = [x for x in t if x['NodeIndex'] == i] @@ -1022,7 +1081,7 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding new['debugName']=e['Data']['debugName']['$value'] new['sectorName']=sectorName new['pivot']=inst['Pivot'] - + new['appearanceName']=NDI_Coll['appearanceName'] if 'Data' in data['cookedInstanceTransforms']['sharedDataBuffer'].keys(): #print(data['cookedInstanceTransforms']) @@ -1077,6 +1136,7 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding instances = [x for x in t if x['NodeIndex'] == i] for inst in instances: light_node=e['Data'] + light_ndata=inst color= light_node['color'] intensity=light_node['intensity'] @@ -1091,19 +1151,75 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding light_obj.location=pos light_obj.rotation_mode='QUATERNION' light_obj.rotation_quaternion=rot + light_obj['flicker']=light_node['flicker'] + light_obj['nodeType']=type A_Light.energy = intensity A_Light.color = get_col(color) if area_shape=='ALS_Capsule': A_Light.shape='ELLIPSE' A_Light.size= light_node['capsuleLength'] + light_obj['capsuleLength']=light_node['capsuleLength'] A_Light.size_y= light_node['radius']*2 + light_obj['radius']=light_node['radius'] elif area_shape=='ALS_Sphere': A_Light.shape='DISK' A_Light.size= light_node['radius']*2 + light_obj['radius']=light_node['radius'] + + pass + + case 'worldStaticParticleNode'|'worldEffectNode'|'worldPopulationSpawnerNode': + #print('worldStaticParticleNode',i) + instances = [x for x in t if x['NodeIndex'] == i] + for idx,inst in enumerate(instances): + o = bpy.data.objects.new( "empty", None ) + o.name=type+'_'+e['Data']['debugName']['$value'] + o['nodeType']=type + o['nodeIndex']=i + o['instance_idx']=idx + o['debugName']=e['Data']['debugName']['$value'] + o['sectorName']=sectorName + if type=='worldStaticParticleNode': + o['particleSystem']=e['Data']['particleSystem']['DepotPath']['$value'] + if type=='worldEffectNode': + o['effect']=e['Data']['effect']['DepotPath']['$value'] + + if type=='worldPopulationSpawnerNode': + o['appearanceName']=e['Data']['appearanceName']['$value'] + o['objectRecordId']=e['Data']['objectRecordId']['$value'] + o['spawnonstart']=e['Data']['spawnOnStart'] + Sector_coll.objects.link(o) + o.location = get_pos(inst) + o.rotation_mode = "QUATERNION" + o.rotation_quaternion = get_rot(inst) + o.scale = get_scale(inst) + o.display_type = 'WIRE' + o.color = (1.0, 0.005, .062, 1) + o.show_wire = True + o.display.show_shadows = False pass + case 'worldStaticSoundEmitterNode': + #print(type) + instances = [x for x in t if x['NodeIndex'] == i] + for idx,inst in enumerate(instances): + o = bpy.data.objects.new( "empty", None ) + o.empty_display_type = 'SPHERE' + o.name=type+'_'+e['Data']['debugName']['$value'] + o['nodeType']=type + o['nodeIndex']=i + o['instance_idx']=idx + o['debugName']=e['Data']['debugName']['$value'] + o['sectorName']=sectorName + o['Settings']=e['Data']['Settings'] + o['eventName']=e['Data']['Settings']['Data']['EventsOnActive'][0]['event']['$value'] + Sector_coll.objects.link(o) + o.location = get_pos(inst) + o.rotation_mode = "QUATERNION" + o.rotation_quaternion = get_rot(inst) + case 'worldCollisionNode': # ______ _____ _ @@ -1142,7 +1258,7 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding srot_q = Quaternion((srot[0],srot[1],srot[2],srot[3])) rot= arot_q @ srot_q loc=(spos[0]+x,spos[1]+y,spos[2]+z) - if shape['ShapeType']=='Box' or shape['ShapeType']=='Capsule': + if shape['ShapeType']=='Box' or shape['ShapeType']=='Capsule' or shape['ShapeType']=='Sphere': #print('Box Collision Node') #pprint(act['Shapes']) @@ -1150,6 +1266,8 @@ def importSectors( filepath, with_mats, remap_depot, want_collisions, am_modding bpy.ops.mesh.primitive_cube_add(size=1/scale_factor, scale=(ssize[0],ssize[1],ssize[2]),location=(loc[0],loc[1],loc[2])) elif shape['ShapeType']=='Capsule': bpy.ops.mesh.primitive_cylinder_add(radius=5/scale_factor, depth=1/scale_factor, scale=(ssize[0],ssize[1],ssize[2]),location=loc) + elif shape['ShapeType']=='Sphere': + bpy.ops.mesh.primitive_uv_sphere_add(radius=5/scale_factor, scale=(ssize[0],ssize[1],ssize[2]),location=loc) crash=C.selected_objects[0] crash.name='NodeDataIndex_'+str(inst['nodeDataIndex'])+'_Actor_'+str(idx)+'_Shape_'+str(s) par_coll=crash.users_collection[0] diff --git a/i_scene_cp77_gltf/jsontool.py b/i_scene_cp77_gltf/jsontool.py index d5c9362..35f5629 100644 --- a/i_scene_cp77_gltf/jsontool.py +++ b/i_scene_cp77_gltf/jsontool.py @@ -75,6 +75,7 @@ def jsonload(filepath): print(f"attempted import of invalid ent.json from: {filepath} this plugin requires jsons generated using the latest version of Wolvenkit") show_message(f"attempted import of invalid ent.json: {base_name} this plugin requires jsons generated using the latest version of Wolvenkit") return 'CANCELLED' + ent_apps= data['Data']['RootChunk']['appearances'] ent_components= data['Data']['RootChunk']['components'] ent_component_data= data['Data']['RootChunk']['compiledData']['Data']['Chunks'] @@ -212,6 +213,7 @@ def jsonload(filepath): if not cp77_addon_prefs.non_verbose: print(f"invalid cfoliage.json found at: {filepath} this plugin requires jsons generated with the latest version of Wolvenkit") show_message(f"invalid cfoliage.json : {base_name} this plugin requires jsons generated with the latest version of Wolvenkit") + return data case _ if base_name.endswith('.refitter.zip'): if not cp77_addon_prefs.non_verbose: diff --git a/i_scene_cp77_gltf/resources/scripts/Import_from_OS.py b/i_scene_cp77_gltf/resources/scripts/Import_from_OS.py new file mode 100644 index 0000000..9ec6011 --- /dev/null +++ b/i_scene_cp77_gltf/resources/scripts/Import_from_OS.py @@ -0,0 +1,183 @@ +# ____ __ _ __ _____ ____ __ +# / __ \/ /_ (_)__ _____/ /_/ ___/____ ____ __ ______ ___ _____ / _/___ ____ __ __/ /_ +# / / / / __ \ / / _ \/ ___/ __/\__ \/ __ \/ __ `/ | /| / / __ \/ _ \/ ___/ / // __ \/ __ \/ / / / __/ +# / /_/ / /_/ / / / __/ /__/ /_ ___/ / /_/ / /_/ /| |/ |/ / / / / __/ / _/ // / / / /_/ / /_/ / /_ +# \____/_.___/_/ /\___/\___/\__//____/ .___/\__,_/ |__/|__/_/ /_/\___/_/ /___/_/ /_/ .___/\__,_/\__/ +# /___/ /_/ /_/ +# +# Object Spawner Group input +# Project path needs to be set to the top level folder of the project that has the meshes in it to find (sector that you created from if there is one) +# groupname is the json filename without .json +# +# + +project_path='C:\\CPMod\\notell' +GroupName='blender_group_1' +with_mats=False + +import bpy +import os +import json +from math import pi,radians,degrees +from mathutils import Vector, Matrix, Euler +import traceback +D=bpy.data +C=bpy.context +coll_scene = C.scene.collection + +def get_position(obj): + pos = Vector((obj['spawnable']['position']['x'],obj['spawnable']['position']['y'],obj['spawnable']['position']['z'])) + rot= Euler((radians(obj['spawnable']['rotation']['pitch']), + radians(obj['spawnable']['rotation']['roll']), + radians(obj['spawnable']['rotation']['yaw']))) + if 'scale' in obj['spawnable'].keys(): + scale =Vector((obj['spawnable']['scale']['x'],obj['spawnable']['scale']['y'],obj['spawnable']['scale']['z'])) + else: + scale=(1,1,1) + return pos,rot,scale + + +def process_group(group,target_coll): + for child in group['childs']: + if child['type']=='group': + coll_target=bpy.data.collections.new(child['name']) + target_coll.children.link(coll_target) + process_group(child,coll_target) + elif child['type']=='object': + process_object(child,target_coll) + +def process_object(obj,parent_coll): + Masters=bpy.data.collections.get("MasterInstances") + spawndata=obj['spawnable']['spawnData'] + spawntype=obj['spawnable']['dataType'] + if spawndata[-5:]=='.mesh' : + meshpath=os.path.join(project_path,'source','raw', spawndata[:-1*len(os.path.splitext(spawndata)[1])]+'.glb').replace('\\', os.sep) + impapps=obj['spawnable']['app'] + groupname = os.path.splitext(os.path.split(meshpath)[-1])[0] + while len(groupname) > 63: + groupname = groupname[:-1] + if groupname not in Masters.children.keys() and os.path.exists(meshpath): + try: + bpy.ops.io_scene_gltf.cp77(with_mats, filepath=meshpath, appearances=impapps) + objs = C.selected_objects + move_coll= coll_scene.children.get( objs[0].users_collection[0].name ) + Masters.children.link(move_coll) + C.scene.collection.children.unlink(move_coll) + except: + print('failed on ',meshpath) + elif groupname not in Masters.children.keys() and not os.path.exists(meshpath): + print('Mesh ', meshpath, ' does not exist') + + if groupname in Masters.children.keys(): + group=Masters.children.get(groupname) + if group: + new=bpy.data.collections.new(groupname) + parent_coll.children.link(new) + new['nodeType']=obj['type'] + #new['entityTemplate']=data['entityTemplate']['DepotPath']['$value'] + new['appearanceName']=obj['spawnable']['app'] + new['obj']=obj + pos,rot,scale=get_position(obj) + for old_obj in group.all_objects: + newobj=old_obj.copy() + new.objects.link(newobj) + newobj.location = pos + newobj.rotation_mode = 'XYZ' + newobj.rotation_euler = rot + newobj.scale = scale + elif spawndata[-4:]=='.ent' : + app=obj['spawnable']['app'] + entpath=os.path.join(project_path,'source','raw', spawndata).replace('\\', os.sep)+'.json' + ent_groupname=os.path.basename(entpath).split('.')[0]+'_'+app + while len(ent_groupname) > 63: + ent_groupname = ent_groupname[:-1] + imported=False + if ent_groupname in Masters.children.keys(): + move_coll=Masters.children.get(ent_groupname) + imported=True + else: + try: + #print('Importing ',entpath, ' using app ',app) + incoll='MasterInstances' + bpy.ops.io_scene_gltf.cp77entity(with_mats, filepath=entpath, appearances=app, inColl=incoll) + move_coll=Masters.children.get(ent_groupname) + imported=True + except: + print(traceback.print_exc()) + print(f"Failed during Entity import on {entpath} from app {app}") + if imported: + group=move_coll + if (group): + groupname=move_coll.name + #print('Group found for ',groupname) + new=bpy.data.collections.new(groupname) + parent_coll.children.link(new) + new['nodeType']='worldEntityNode' + new['debugName']=obj['name'] + new['entityTemplate']=spawndata + new['appearanceName']=obj['spawnable']['app'] + + pos,rot,scale=get_position(obj) + rot=rot.to_quaternion() + new['ent_rot']=rot + new['ent_pos']=pos + inst_trans_mat=Matrix.LocRotScale(pos,rot,scale) + for child in group.children: + newchild=bpy.data.collections.new(child.name) + new.children.link(newchild) + for old_obj in child.objects: + obj=old_obj.copy() + obj.color = (0.567942, 0.0247339, 0.600028, 1) + newchild.objects.link(obj) + obj.matrix_local= inst_trans_mat @ obj.matrix_local + if 'Armature' in obj.name: + obj.hide_set(True) + for old_obj in group.objects: + obj=old_obj.copy() + obj.color = (0.567942, 0.0247339, 0.600028, 1) + new.objects.link(obj) + obj.matrix_local= inst_trans_mat @ obj.matrix_local + if 'Armature' in obj.name: + obj.hide_set(True) + if len(group.all_objects)>0: + new['matrix']=group.all_objects[0].matrix_world + elif spawntype=='Decals': + vert = [(-0.5, -0.5, 0.0), (0.5, -0.5, 0.0), (-0.5, 0.5, 0.0), (0.5,0.5, 0.0)] + fac = [(0, 1, 3, 2)] + pl_data = bpy.data.meshes.new("PL") + pl_data.from_pydata(vert, [], fac) + pl_data.uv_layers.new(name="UVMap") + + o = bpy.data.objects.new("Decal_Plane", pl_data) + o['nodeType']='worldStaticDecalNode' + o['decal']=spawndata + o['debugName']=obj['name'] + o['horizontalFlip']=obj['spawnable']['horizontalFlip'] + o['verticalFlip']=obj['spawnable']['verticalFlip'] + o['alpha']=obj['spawnable']['alpha'] + o['appearanceName']=obj['spawnable']['app'] + + parent_coll.objects.link(o) + pos,rot,scale=get_position(obj) + o.location = pos + o.rotation_mode = 'XYZ' + o.rotation_euler = rot + o.scale = scale + +if "MasterInstances" not in coll_scene.children.keys(): + coll_target=bpy.data.collections.new("MasterInstances") + coll_scene.children.link(coll_target) +else: + coll_target=bpy.data.collections.get("MasterInstances") + + + +sectpathin = os.path.join(project_path,GroupName+'.json') +#sectpathin='C:\\CPMod\\notell\\blender_group5.json' +with open(sectpathin, 'r') as inputfile: + j = json.load(inputfile) +print('loaded json for ', GroupName) +group_coll=bpy.data.collections.new(GroupName+'.json') +coll_scene.children.link(group_coll) +process_group(j,group_coll) +coll_target.hide_viewport=True diff --git a/i_scene_cp77_gltf/resources/scripts/export_to_OS.py b/i_scene_cp77_gltf/resources/scripts/export_to_OS.py new file mode 100644 index 0000000..3502fc9 --- /dev/null +++ b/i_scene_cp77_gltf/resources/scripts/export_to_OS.py @@ -0,0 +1,485 @@ +# +# ____ __ _ __ _____ ____ __ __ +# / __ \/ /_ (_)__ _____/ /_/ ___/____ ____ __ ______ ___ _____ / __ \__ __/ /_____ __ __/ /_ +# / / / / __ \ / / _ \/ ___/ __/\__ \/ __ \/ __ `/ | /| / / __ \/ _ \/ ___/ / / / / / / / __/ __ \/ / / / __/ +# / /_/ / /_/ / / / __/ /__/ /_ ___/ / /_/ / /_/ /| |/ |/ / / / / __/ / / /_/ / /_/ / /_/ /_/ / /_/ / /_ +# \____/_.___/_/ /\___/\___/\__//____/ .___/\__,_/ |__/|__/_/ /_/\___/_/ \____/\__,_/\__/ .___/\__,_/\__/ +# /___/ /_/ /_/ # +# +# Script to generate Object Spawner jsons from the current selection +# Written by Simarilius, OS by KeanuWheeze +# Initial version 15/8/24 +# v0.2 +# +# Import some sectors +# Select some stuff +# set the groupname and project folder variables +# Run the script, it saves a json with the OS group in the top level project folder +# Copy it to Cyberpunk 2077\bin\x64\plugins\cyber_engine_tweaks\mods\entSpawner\data\objects +# Spawn it and enjoy/modify +# when your happy with it use the wscript to import to wkit and convert to axl mod +# +# Comments/Suggestions welcome, feel free to ping me on the wkit discord. (use worldediting or blenderaddon channels) + +GroupName='blender_group_1' +ProjectFolder = 'C:\\CPMod\\notell' + +# Can try autogenerate collisions, options are NONE, ALL, STRUCT (trys to just do walls/floors/ceilings) +# Any objects with OS_Coll or physicsCollider in the name will get treated as collisions regardless of this +# first if for manual coll additions, second is the naming from the plugin collision generator thing +GENERATE_COLLISIONS='NONE' + +import bpy +import os +import json +from math import pi,radians,degrees +D=bpy.data +C=bpy.context + + + +def set_pos(obj): + #print(inst) + position={} + position['w'] = float("{:.9g}".format(0)) + position['x']= float("{:.9g}".format(obj.location[0])) + position['y'] = float("{:.9g}".format(obj.location[1])) + position['z'] = float("{:.9g}".format(obj.location[2])) + return position + +def set_rot(obj): + rotation={} + obj.rotation_mode='XYZ' + rotation['pitch'] = float("{:.9g}".format(degrees(obj.rotation_euler[0] ))) + rotation['roll'] = float("{:.9g}".format(degrees(obj.rotation_euler[1] ))) + rotation['yaw'] = float("{:.9g}".format(degrees(obj.rotation_euler[2] ))) + return rotation + +def set_scale(obj): + scale={} + scale['x'] = float("{:.9g}".format(obj.scale[0] )) + scale['y'] = float("{:.9g}".format(obj.scale[1] )) + scale['z'] = float("{:.9g}".format(obj.scale[2] )) + return scale + +def save_selected(): + selected=[] + for obj in bpy.context.selected_objects: + selected.append(obj) + return selected + +def retrieve_selected(list): + for obj in list: + try: + obj.select_set(True) + except: + print('invalid') + +def find_debugName(obj): + debugName=None + if 'debugName' in obj.keys(): + return obj['debugName'] + if 'debugName' in obj.users_collection[0].keys(): + debugName=obj.users_collection[0]['debugName'] + else: + if coll_parents.get(obj.users_collection[0].name)!="Scene Collection" and 'debugName' in D.collections[coll_parents.get(obj.users_collection[0].name)]: + debugName=D.collections[coll_parents.get(obj.users_collection[0].name)]['debugName'] + else: + if coll_parents.get(coll_parents.get(obj.users_collection[0].name.name)) and 'debugName' in D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))]: + debugName=D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))]['debugName'] + return debugName + +def find_nodeType(obj): + nodeType=None + if 'nodeType' in obj.keys(): + return obj['nodeType'] + if obj.users_collection[0].name!="Scene Collection" and 'nodeType' in obj.users_collection[0].keys(): + nodeType=obj.users_collection[0]['nodeType'] + elif coll_parents.get(obj.users_collection[0].name)!="Scene Collection" and 'nodeType' in D.collections[coll_parents.get(obj.users_collection[0].name)]: + nodeType=D.collections[coll_parents.get(obj.users_collection[0].name)]['nodeType'] + + return nodeType + +def traverse_tree(t): + yield t + for child in t.children: + yield from traverse_tree(child) + +def parent_lookup(coll): + parent_lookup = {} + for coll in traverse_tree(coll): + for c in coll.children.keys(): + parent_lookup.setdefault(c, coll.name) + return parent_lookup + +def new_group(groupname): + return {"rot": {"pitch": 0,"yaw": 0,"roll": 0}, "pos": { "x": 0,"y": 0, "w": 0, "z": 0 },"childs":[],"name": groupname,"isUsingSpawnables": True, "headerOpen": True,"loadRange": 1000, "autoLoad": False, "type": "group"} + +def new_object(OStype,name, pos, rot,spawnData): + obj=new_group(name) + obj['type']='object' + obj['name']=name + obj['spawnableHeaderOpen']=False + obj['spawnable']= { + "modulePath": OStype[0], + "rotationRelative": False, + "position": pos, + "spawnData": spawnData, + "app": "", + "dataType": OStype[1], + "rotation": rot + } + return obj + +def new_collision(group, obj, name): + colltarget = group['childs'][mesh_colls]['childs'] + obj.rotation_mode='XYZ' + rot=set_rot(obj) + pos=set_pos(obj) + scale=set_scale(obj) + extents={'x':obj.dimensions[0]/2,'y':obj.dimensions[1]/2,'z':obj.dimensions[2]/2} + collpos={'x':pos['x'],'y':pos['y'],'z':pos['z']} + colltarget.append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": obj.name, + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "collision/collider", + "rotationRelative": False, + "previewed": True, + "radius": 1, # radius for sphere and capsule + "material": 31, + "position": collpos, + "height": 1, # capsule length + "spawnData": "base\\spawner\\empty_entity.ent", + "extents": extents, + "preset": 33, + "rotation": rot, + "app": "", + "dataType": "Collision Shape", + "shape": 0 # 0=box, 1=capsue, 2=sphere from entspawner code + }, + "headerOpen": False + }) + + + + +coll_scene = C.scene.collection +coll_parents = parent_lookup(coll_scene) + + +exported=[] +group=new_group(GroupName) +group['childs'].append(new_group('walls')) +walls=0 +group['childs'].append(new_group('floors')) +floors=1 +group['childs'].append(new_group('ceilings')) +ceilings=2 +group['childs'].append(new_group('Decals')) +decals=3 +group['childs'].append(new_group('Entities')) +entities=4 +group['childs'].append(new_group('Meshes')) +meshes=5 +group['childs'].append(new_group('Sounds')) +sounds=6 +group['childs'].append(new_group('Effects_and_Particles')) +effects=7 +group['childs'].append(new_group('Struct_Collisions')) +struct_colls=8 +group['childs'].append(new_group('mesh_collisions')) +mesh_colls=9 + +objs=bpy.context.selected_objects +for obj in objs: + nodeType=find_nodeType(obj) + + match nodeType: + # check for decals + case 'worldStaticDecalNode': + pos=set_pos(obj) + rot=set_rot(obj) + + + if 'horizontalFlip' in obj.keys(): + horizontalFlip=bool(obj['horizontalFlip']) + else: + horizontalFlip=False + if 'verticalFlip' in obj.keys(): + verticalFlip=bool(obj['verticalFlip']) + else: + verticalFlip=False + + group['childs'][decals]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": obj.name, + "autoLoad": False, + "loadRange": 1000, + "spawnable": {"modulePath": "visual/decal", + "rotationRelative": False, + "alpha": 1, + "horizontalFlip": horizontalFlip, + "verticalFlip": verticalFlip, + "position": set_pos(obj), + "spawnData": obj['decal'], + "scaleLocked": True, + "autoHideDistance": 150, + "scale": set_scale(obj), + "app":obj['appearanceName'], + "dataType": "Decals", + "rotation": rot}, + "headerOpen": False}) + # check for entities + case 'worldEntityNode': + if 'nodeType' in obj.keys(): + ent=obj + elif 'nodeType' in obj.users_collection[0].keys(): + ent=obj.users_collection[0] + elif 'nodeType' in D.collections[coll_parents.get(obj.users_collection[0].name)]: + ent=D.collections[coll_parents.get(obj.users_collection[0].name)] + elif 'nodeType' in D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))]: + ent=D.collections[coll_parents.get(coll_parents.get(obj.users_collection[0].name.name))] + if ent.name not in exported: + rot={'pitch': float("{:.9g}".format(degrees(ent['ent_rot'][0]))),'roll': float("{:.9g}".format(degrees(ent['ent_rot'][1]))),'yaw': float("{:.9g}".format(degrees(ent['ent_rot'][2])))} + pos={'x': float("{:.9g}".format(ent['ent_pos'][0])),'y': float("{:.9g}".format(ent['ent_pos'][1])),'z': float("{:.9g}".format(ent['ent_pos'][2])),'w': float("{:.9g}".format(0))} + + group['childs'][entities]['childs'].append({"spawnableHeaderOpen": False, + "type": "object", + "name": ent.name, + "autoLoad": False, + "loadRange": 1000, + "spawnable": {"modulePath": "entity/entityTemplate", + "rotationRelative": False, + "position": pos, + "instanceData": [], + "spawnData": ent['entityTemplate'], + "instanceDataChanges": [], + "app": ent['appearanceName'], + "dataType": "Entity Template", + "rotation": rot + }, + "headerOpen": False + }) + exported.append(ent.name) + case "worldPopulationSpawnerNode": + rot=set_rot(obj) + group['childs'][entities]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": "Entity Record", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "entity/entityRecord", + "rotationRelative": False, + "position": set_pos(obj), + "instanceData": [], + "spawnData": obj['objectRecordId'], + "instanceDataChanges": [], + "app": obj['appearanceName'], + "dataType": "Entity Record", + "rotation": rot + }, + "headerOpen": False + }) + pass + + case "worldStaticLightNode": + rot=set_rot(obj) + OSobj= { + "spawnableHeaderOpen": False, + "type": "object", + "name": "Light", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "light/light", + "intensity": obj.data.energy, + "radius": 15, + "spawnData": "base\\spawner\\empty_entity.ent", + "capsuleLength": 1, + "autoHideDistance": 45, + "flickerStrength": obj['flicker']['flickerStrength'], + "flickerPeriod": obj['flicker']['flickerPeriod'], + "dataType": "Static Light", + "rotationRelative": False, + "innerAngle": 20, + "outerAngle": 60, + "color": [ + obj.data.color[0], + obj.data.color[1], + obj.data.color[2] + ], + "position": set_pos(obj), + "scaleVolFog": 0, + "temperature": -1, + "lightType": 0, + "app": "", + "flickerOffset": 0, + "rotation": rot + }, + "headerOpen": False + } + if 'capsuleLength' in obj.keys(): + OSobj['capsuleLength']=obj['capsuleLength'] + if 'radius' in obj.keys(): + OSobj['radius']=obj['radius'] + group['childs'][entities]['childs'].append(OSobj) + + pass + + case "worldEffectNode": + rot=set_rot(obj) + group['childs'][effects]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": "Effect", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "visual/effect", + "spawnData": obj['effect'], + "rotationRelative": False, + "dataType": "Effects", + "app": "", + "rotation": rot, + "position": set_pos(obj) + }, + "headerOpen": False + }) + pass + case "worldStaticParticleNode": + rot=set_rot(obj) + group['childs'][effects]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": "Particle", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "visual/particle", + "emissionRate": 1, + "respawnOnMove": False, + "position": set_pos(obj), + "spawnData": obj['particleSystem'], + "rotation": rot, + "app": "", + "dataType": "Particles", + "rotationRelative": False + }, + "headerOpen": False + }) + pass + case "worldStaticSoundEmitterNode": + group['childs'][sounds]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": "Sound", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "visual/audio", + "spawnData": obj['eventName'], + "radius": 5, + "rotationRelative": False, + "dataType": "Sounds", + "app": "", + "rotation": set_rot(obj), + "position": set_pos(obj) + }, + "headerOpen": False + }) + pass + + case "worldRotatingMeshNode": + OS_obj=new_object("mesh/rotatingMesh",obj.name,set_pos(obj),set_rot(obj),obj.users_collection[0]['mesh']) + OS_obj['axis']=obj.users_collection[0]['rot_axis'] + OS_obj["reverse"]='reverseDirection' + OS_obj["duration"]='fullRotationTime' + + case "worldCollisionNode": + new_collision(group, obj,obj.name) + pass + case "worldClothMeshNode": + obj.rotation_mode='XYZ' + rot=set_rot(obj) + pos=set_pos(obj) + scale=set_scale(obj) + group['childs'][meshes]['childs'].append({ + "spawnableHeaderOpen": False, + "type": "object", + "name": "Cloth Mesh", + "autoLoad": False, + "loadRange": 1000, + "spawnable": { + "modulePath": "mesh/clothMesh", + "rotationRelative": False, + "scale": scale, + "rotation": rot, + "spawnData": obj.users_collection[0]['mesh'], + "affectedByWind": True, + "scaleLocked": False, + "position": pos, + "app": obj['appearanceName'], + "dataType": "Cloth Mesh", + "collisionType": 4 + }, + "headerOpen": False + }) + pass + # none of the above then see if it has a mesh and treat it as a static + case _: + if obj.users_collection[0].name not in exported and 'mesh' in obj.users_collection[0].keys() : + target=group['childs'][meshes]['childs'] + colltarget = group['childs'][mesh_colls]['childs'] + + mesh=obj.users_collection[0]['mesh'] + name=os.path.basename(mesh).split('.')[0] + if 'wall' in name and 'decal' not in name: + target = group['childs'][walls]['childs'] + colltarget = group['childs'][struct_colls]['childs'] + elif 'floor' in name: + target = group['childs'][floors]['childs'] + colltarget = group['childs'][struct_colls]['childs'] + elif 'ceil' in name: + target = group['childs'][ceilings]['childs'] + colltarget = group['childs'][struct_colls]['childs'] + obj.rotation_mode='XYZ' + rot=set_rot(obj) + print(rot) + pos=set_pos(obj) + scale=set_scale(obj) + target.append({"spawnable":{ + "dataType":"Static Mesh", + "rotation":rot, + "scale":scale, + "rotationRelative":False, + "app":obj.users_collection[0]['appearanceName'], + "scaleLocked":True, + "modulePath":"mesh/mesh", + "position":pos, + "spawnData":mesh}, + "headerOpen":False, + "spawnableHeaderOpen":False, + "autoLoad":False, + "loadRange":1000, + "type":"object", + "name":name}) + + exported.append(obj.users_collection[0].name) + if GENERATE_COLLISIONS=='ALL' or (GENERATE_COLLISIONS=='STRUCT' and ('wall' in name and 'decal' not in name and 'door' not in name or 'floor' in name or 'ceil' in name)): + + new_collision(group, obj, name+"_ColliderBox") + + elif 'OS_Coll' in obj.name or 'physicsCollider' in obj.name: + new_collision(group, obj,obj.name) + +sectpathout = os.path.join(ProjectFolder,GroupName+'.json') + +with open(sectpathout, 'w') as outfile: + json.dump(group, outfile,indent=2) \ No newline at end of file