X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=blender_export.py;h=afb0f78c41c3432db71699bcdb87044aa6604ce4;hb=e36cb7e44611855d39ba84710a7007ca659e9cd5;hp=3e7567d8b09734f4616c976a4f585a5c42dde575;hpb=51a9d6b9321b951b79dff0d52a82116c678ad8aa;p=carveJwlIkooP6JGAAIwe30JlM.git diff --git a/blender_export.py b/blender_export.py index 3e7567d..afb0f78 100644 --- a/blender_export.py +++ b/blender_export.py @@ -1,5 +1,18 @@ -import bpy, math +import bpy, math, gpu from ctypes import * +from gpu_extras.batch import batch_for_shader + +bl_info = { + "name":"Carve exporter", + "author": "Harry Godden (hgn)", + "version": (0,1), + "blender":(3,1,0), + "location":"Export", + "descriptin":"", + "warning":"", + "wiki_url":"", + "category":"Import/Export", +} class model(Structure): _pack_ = 1 @@ -9,11 +22,6 @@ class model(Structure): ("layer_count",c_uint32), ("marker_count",c_uint32)] -class sdf_primative(Structure): - _pack_ = 1 - _fields_ = [("origin",c_float*4), - ("info",c_float*4)] - class submodel(Structure): _pack_ = 1 _fields_ = [("indice_start",c_uint32), @@ -22,15 +30,21 @@ class submodel(Structure): ("vertex_count",c_uint32), ("bbx",(c_float*3)*2), ("pivot",c_float*3), - ("sdf",sdf_primative), - ("sdf_type",c_int32), - ("name",c_char*32)] + ("q",c_float*4), + ("name",c_char*32), + ("material",c_char*32)] + +class classtype_gate(Structure): + _pack_ = 1 + _fields_ = [("target",c_uint32)] class marker(Structure): _pack_ = 1 _fields_ = [("co",c_float*3), ( "q",c_float*4), ( "s",c_float*3), + ("classtype",c_uint32), + ("offset",c_uint32), ("name",c_char*32)] class model_vert(Structure): @@ -40,93 +54,16 @@ class model_vert(Structure): ("colour",c_float*4), ("uv",c_float*2)] -def v4_dot( a, b ): - return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*a[3] - -def v4_length( a ): - return math.sqrt( v4_dot(a,a) ) - -def m3x3_mul( a, b, d ): - a00 = a[0][0] - a01 = a[0][1] - a02 = a[0][2] - a10 = a[1][0] - a11 = a[1][1] - a12 = a[1][2] - a20 = a[2][0] - a21 = a[2][1] - a22 = a[2][2] - b00 = b[0][0] - b01 = b[0][1] - b02 = b[0][2] - b10 = b[1][0] - b11 = b[1][1] - b12 = b[1][2] - b20 = b[2][0] - b21 = b[2][1] - b22 = b[2][2] - d[0][0] = a00*b00 + a10*b01 + a20*b02 - d[0][1] = a01*b00 + a11*b01 + a21*b02 - d[0][2] = a02*b00 + a12*b01 + a22*b02 - d[1][0] = a00*b10 + a10*b11 + a20*b12 - d[1][1] = a01*b10 + a11*b11 + a21*b12 - d[1][2] = a02*b10 + a12*b11 + a22*b12 - d[2][0] = a00*b20 + a10*b21 + a20*b22 - d[2][1] = a01*b20 + a11*b21 + a21*b22 - d[2][2] = a02*b20 + a12*b21 + a22*b22 - -def q_m3x3( q, d ): - l = v4_length(q) - s = 2.0 if l > 0.0 else 0.0 - xx = s*q[0]*q[0] - xy = s*q[0]*q[1] - wx = s*q[3]*q[0] - yy = s*q[1]*q[1] - yz = s*q[1]*q[2] - wy = s*q[3]*q[1] - zz = s*q[2]*q[2] - xz = s*q[0]*q[2] - wz = s*q[3]*q[2] - d[0][0] = 1.0 - yy - zz - d[1][1] = 1.0 - xx - zz - d[2][2] = 1.0 - xx - yy - d[0][1] = xy + wz - d[1][2] = yz + wx - d[2][0] = xz + wy - d[1][0] = xy - wz - d[2][1] = yz - wx - d[0][2] = xz - wy - -def m3x3_q( m, q ): - diag = m[0][0] + m[1][1] + m[2][2] - if diag >= 0.0: - r = math.sqrt( 1.0 + diag ) - rinv = 0.5 / r - q[0] = rinv * (m[1][2] - m[2][1]) - q[1] = rinv * (m[2][0] - m[0][2]) - q[2] = rinv * (m[0][1] - m[1][0]) - q[3] = r * 0.5 - elif m[0][0] >= m[1][1] and m[0][0] >= m[2][2]: - r = math.sqrt( 1.0 - m[1][1] - m[2][2] + m[0][0] ) - rinv = 0.5 / r - q[0] = r * 0.5 - q[1] = rinv * (m[0][1] + m[1][0]) - q[2] = rinv * (m[0][2] + m[2][0]) - q[3] = rinv * (m[1][2] - m[2][1]) - elif m[1][1] >= m[2][2]: - r = math.sqrt( 1.0 - m[0][0] - m[2][2] + m[1][1] ) - rinv = 0.5 / r - q[0] = rinv * (m[0][1] + m[1][0]) - q[1] = r * 0.5 - q[2] = rinv * (m[1][2] + m[2][1]) - q[3] = rinv * (m[2][0] - m[0][2]) - else: - r = math.sqrt( 1.0 - m[0][0] - m[1][1] + m[2][2] ) - rinv = 0.5 / r - q[0] = rinv * (m[0][2] + m[2][0]) - q[1] = rinv * (m[1][2] + m[2][1]) - q[2] = r * 0.5 - q[3] = rinv * (m[0][1] - m[1][0]) +def submesh_set_transform( sm, obj ): + sm.pivot[0] = obj.matrix_world.translation[0] + sm.pivot[1] = obj.matrix_world.translation[2] + sm.pivot[2] = -obj.matrix_world.translation[1] + + quat = obj.matrix_world.to_quaternion() + sm.q[0] = quat[1] + sm.q[1] = quat[3] + sm.q[2] = -quat[2] + sm.q[3] = quat[0] def write_model(name): fp = open(F"/home/harry/Documents/carve/models/{name}.mdl", "wb") @@ -137,12 +74,41 @@ def write_model(name): header.vertex_count = 0 header.indice_count = 0 header.layer_count = 0 - + header.marker_count = 1 + + mesh_cache = {} layers = [] - markers = [] vertex_buffer = [] indice_buffer = [] + print( F"Create mode {name}" ) + + rootmarker = marker() + rootmarker.co[0] = 0 + rootmarker.co[1] = 0 + rootmarker.co[2] = 0 + rootmarker.q[0] = 0 + rootmarker.q[1] = 0 + rootmarker.q[2] = 0 + rootmarker.q[3] = 1 + rootmarker.s[0] = 1 + rootmarker.s[1] = 1 + rootmarker.s[2] = 1 + rootmarker.name = "".encode('utf-8') + rootmarker.offset = 0 + rootmarker.classtype = 0 + + markers = [ rootmarker ] # aka entities + entdata_structs = [] + entdata_offset = 0 + + entity_count = 1 + + for obj in collection.objects: + if obj.type == 'EMPTY': + obj.cv_data.uid = entity_count + entity_count += 1 + for obj in collection.objects: if obj.type == 'EMPTY': mk = marker() @@ -161,88 +127,147 @@ def write_model(name): mk.s[1] = obj.scale[2] mk.s[2] = obj.scale[1] mk.name = obj.name.encode('utf-8') + mk.offset = entdata_offset + + classtype = obj.cv_data.classtype + + if classtype == 'k_classtype_gate': + mk.classtype = 1 + entdata_offset += sizeof( classtype_gate ) + + gate = classtype_gate() + gate.target = 0 + if obj.cv_data.target != None: + gate.target = obj.cv_data.target.cv_data.uid + + entdata_structs += [gate] + + elif classtype == 'k_thingummybob': + pass markers += [mk] header.marker_count += 1 elif obj.type == 'MESH': + default_mat = c_uint32(69) + default_mat.name = "" + + if obj.data.name in mesh_cache: + ref = mesh_cache[obj.data.name] + for material_id, mref in enumerate(ref['sm']): + print(F" Link submesh({ref['users']}) '{obj.name}:{mat.name}'") + + sm = submodel() + sm.indice_start = mref['indice_start'] + sm.indice_count = mref['indice_count'] + sm.vertex_start = mref['vertex_start'] + sm.vertex_count = mref['vertex_count'] + sm.name = obj.name.encode('utf-8') + sm.material = mref['material'] + sm.bbx = mref['bbx'] + submesh_set_transform( sm, obj ) + layers += [sm] + header.layer_count += 1 + + ref['users'] += 1 + continue + + ref = mesh_cache[obj.data.name] = {} + ref['users'] = 0 + ref['sm'] = [] + dgraph = bpy.context.evaluated_depsgraph_get() data = obj.evaluated_get(dgraph).data data.calc_loop_triangles() data.calc_normals_split() - sm = submodel() - sm.indice_start = header.indice_count - sm.vertex_start = header.vertex_count - sm.vertex_count = len(data.vertices) - sm.indice_count = len(data.loop_triangles)*3 - sm.sdf_type = 0 - sm.pivot[0] = obj.matrix_world.translation[0] - sm.pivot[1] = obj.matrix_world.translation[2] - sm.pivot[2] = -obj.matrix_world.translation[1] - - for i in range(3): - sm.bbx[0][i] = 999999 - sm.bbx[1][i] = -999999 - - if F"{obj.name}.sdf_cone" in bpy.data.objects: - cone = bpy.data.objects[F"{obj.name}.sdf_cone"] - sm.sdf.origin[0] = cone.location[0] - sm.sdf.origin[1] = cone.location[2] + cone.scale[1]*2.0 - sm.sdf.origin[2] = -cone.location[1] - sm.sdf.origin[3] = 0.0 - - lo = cone.scale[0] - la = cone.scale[1]*2.0 - lh = math.sqrt(lo*lo+la*la) - - sm.sdf.info[0] = lo - sm.sdf.info[1] = la - sm.sdf.info[2] = lo/lh - sm.sdf.info[3] = la/lh - - sm.sdf_type = 1 - - sm.name = obj.name.encode('utf-8') - - for vert in data.vertices: - v = model_vert() - v.co[0] = vert.co[0] - v.co[1] = vert.co[2] - v.co[2] = -vert.co[1] - v.colour[0] = 1.0 - v.colour[1] = 1.0 - v.colour[2] = 1.0 - v.colour[3] = 1.0 - vertex_buffer += [v] - + mat_list = data.materials if len(data.materials) > 0 else [default_mat] + for material_id, mat in enumerate(mat_list): + mref = {} + + sm = submodel() + sm.indice_start = header.indice_count + sm.vertex_start = header.vertex_count + sm.vertex_count = 0 + sm.indice_count = 0 + submesh_set_transform( sm, obj ) + for i in range(3): - sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] ) - sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] ) - - for l in data.loops: - pvert = vertex_buffer[l.vertex_index + sm.vertex_start] - norm = l.normal - pvert.norm[0] = norm[0] - pvert.norm[1] = norm[2] - pvert.norm[2] = -norm[1] - - #if data.vertex_colors: - # colour = data.vertex_colors.active.data[ l.index ].color - # pvert.colour[0] = colour[0] - - if data.uv_layers: - uv = data.uv_layers.active.data[ l.index ].uv - pvert.uv[0] = uv[0] - pvert.uv[1] = uv[1] + sm.bbx[0][i] = 999999 + sm.bbx[1][i] = -999999 + + sm.name = obj.name.encode('utf-8') + sm.material = mat.name.encode('utf-8') + print( F" Creating submesh '{obj.name}:{mat.name}'" ) + boffa = {} + + hit_count = 0 + miss_count = 0 - for tri in data.loop_triangles: - indice_buffer += [c_uint32(tri.vertices[_]) for _ in range(3)] + # Write the vertex / indice data + # + for tri_index, tri in enumerate(data.loop_triangles): + if tri.material_index != material_id: + continue - layers += [sm] - header.layer_count += 1 - header.vertex_count += sm.vertex_count - header.indice_count += sm.indice_count + for j in range(3): + vert = data.vertices[tri.vertices[j]] + + co = vert.co + norm = data.loops[tri.loops[j]].normal + uv = (0,0) + if data.uv_layers: + uv = data.uv_layers.active.data[tri.loops[j]].uv + + key = (round(co[0],4),round(co[1],4),round(co[2],4),\ + round(norm[0],4),round(norm[1],4),round(norm[2],4),\ + round(uv[0],4),round(uv[1],4)) + + if key in boffa: + indice_buffer += [boffa[key]] + hit_count += 1 + else: + miss_count += 1 + index = c_uint32(sm.vertex_count) + sm.vertex_count += 1 + + boffa[key] = index + + indice_buffer += [index] + + v = model_vert() + v.co[0] = co[0] + v.co[1] = co[2] + v.co[2] = -co[1] + v.norm[0] = norm[0] + v.norm[1] = norm[2] + v.norm[2] = -norm[1] + v.uv[0] = uv[0] + v.uv[1] = uv[1] + v.colour[0] = 1.0 + v.colour[1] = 1.0 + v.colour[2] = 1.0 + v.colour[3] = 1.0 + vertex_buffer += [v] + + for i in range(3): + sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] ) + sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] ) + + sm.indice_count += 1 + + layers += [sm] + header.layer_count += 1 + header.vertex_count += sm.vertex_count + header.indice_count += sm.indice_count + + mref['indice_start'] = sm.indice_start + mref['indice_count'] = sm.indice_count + mref['vertex_start'] = sm.vertex_start + mref['vertex_count'] = sm.vertex_count + mref['bbx'] = sm.bbx + mref['material'] = sm.material + ref['sm'] += [mref] fp.write( bytearray( header ) ) for l in layers: @@ -253,8 +278,114 @@ def write_model(name): fp.write( bytearray(v) ) for i in indice_buffer: fp.write( bytearray(i) ) + for ed in entdata_structs: + fp.write( bytearray(ed) ) fp.close() -for col in bpy.data.collections["export"].children: - write_model( col.name ) +# Clicky clicky GUI +# ------------------------------------------------------------------------------ + +cv_view_draw_handler = None +cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') + +def cv_draw(): + global cv_view_shader + cv_view_shader.bind() + gpu.state.depth_mask_set(False) + gpu.state.line_width_set(2.0) + gpu.state.face_culling_set('BACK') + gpu.state.depth_test_set('NONE') + gpu.state.blend_set('ADDITIVE') + + verts = [] + colours = [] + + for obj in bpy.context.collection.all_objects: + if obj.cv_data.classtype == 'k_classtype_gate': + if obj.cv_data.target != None: + p0 = obj.location + p1 = obj.cv_data.target.location + verts += [(p0[0],p0[1],p0[2])] + verts += [(p1[0],p1[1],p1[2])] + colours += [(0,1,0,1.0),(1,0,0,1.0)] + + lines = batch_for_shader(\ + cv_view_shader, 'LINES', \ + { "pos":verts, "color":colours }) + + lines.draw( cv_view_shader ) + +def cv_poll_target(scene, obj): + if obj == bpy.context.active_object: + return False + if obj.cv_data.classtype == 'k_classtype_none': + return False + return True + +class CV_OBJ_SETTINGS(bpy.types.PropertyGroup): + uid: bpy.props.IntProperty( name="" ) + + target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \ + poll=cv_poll_target ) + + classtype: bpy.props.EnumProperty( + name="Format", + items = [ + ('k_classtype_none', "k_classtype_none", "", 0), + ('k_classtype_gate', "k_classtype_gate", "", 1), + ]) + +class CV_OBJ_PANEL(bpy.types.Panel): + bl_label="Entity Config" + bl_idname="SCENE_PT_cv_entity" + bl_space_type='PROPERTIES' + bl_region_type='WINDOW' + bl_context="object" + + def draw(_,context): + active_object = bpy.context.active_object + if active_object == None: return + _.layout.prop( active_object.cv_data, "classtype" ) + _.layout.prop( active_object.cv_data, "target" ) + +class CV_INTERFACE(bpy.types.Panel): + bl_idname = "VIEW3D_PT_carve" + bl_label = "Carve" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Carve" + + def draw(_, context): + layout = _.layout + layout.operator( "carve.compile_all" ) + +class CV_COMPILE(bpy.types.Operator): + bl_idname="carve.compile_all" + bl_label="Compile All" + + def execute(_,context): + for col in bpy.data.collections["export"].children: + write_model( col.name ) + + return {'FINISHED'} + +classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE] + +def register(): + global cv_view_draw_handler + + for c in classes: + bpy.utils.register_class(c) + + bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS) + cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\ + cv_draw,(),'WINDOW','POST_VIEW') + +def unregister(): + global cv_view_draw_handler + + for c in classes: + bpy.utils.unregister_class(c) + + bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')