c-d-a/io_export_qmap

[Request] Import Conversion

Closed this issue · 3 comments

Like the title say, could we request a version of the script to import instead, are there any plans for it in sight?

I have a work-in-progress version, mostly for testing UV code. It only handles brush geometry, so the usefulness is limited (since editors often allow you to export to obj directly). It does not import materials, so ideally you'll want all the materials in the scene before import.
I've ran into some Blender bugs, so the plan is to see if they get fixed for v4.2, and maybe finalize the importer then. Depending on what the new "extension platform" will look like in 4.2, I'll also decide if import/export should be a combined repo or two separate ones.
I also have some optimizations and rewrites for the exporter, but I've been putting them off for months now. I'll try to squeeze them in by the end of the week.
Closing this one, but feel free to post if you run into problems.

Thanks alot for the support, i've tried with 3.6.
It seams at the moment creates a base mesh of the map. Maybe you dont need it however made a script for importing the textures based on the materials name since they're the same. at least for quake.

material

Script 1: Q4TextureFinder.py
This is to be called by the material wilst being added, it searches in the main folder path of quake 4 first if its not there or doesn't exist it looks over the related .PAK files

import zipfile
import os
import bpy

# .pk4 files with textures
pk4_files = [
    "pak010.pk4", "pak011.pk4", "pak012.pk4",
    "pak013.pk4", "pak014.pk4", "pak016.pk4", "pak019.pk4"
]

# Preferred file type
preferred_extension = ".tga"

def search_in_base_path(base_path, search_path):
    full_path = os.path.join(base_path, search_path)
    if os.path.exists(full_path):
        print(f"Found in base path: {full_path}")
        return [full_path]
    return None

def search_in_pk4_files(pk4_files, search_path):
    matches = []
    for pk4_file in pk4_files:
        with zipfile.ZipFile(pk4_file, 'r') as pk4:
            file_list = pk4.namelist()
            matches.extend([file for file in file_list if file.startswith(search_path)])
            if matches:
                print(f"Found matches in {pk4_file}:")
                for match in matches:
                    print(match)
                # Check for preferred file type
                preferred_file = next((file for file in matches if file.endswith(preferred_extension)), None)
                if preferred_file:
                    print(f"Preferred file found: {preferred_file}")
                    return preferred_file
    return None

def extract_and_import_texture(pk4, file_path, material_name):
    temp_dir = bpy.app.tempdir
    temp_file_path = os.path.join(temp_dir, os.path.basename(file_path))
    with pk4.open(file_path) as source, open(temp_file_path, 'wb') as target:
        target.write(source.read())
    
    # Import
    if temp_file_path.endswith(".tga") or temp_file_path.endswith(".dds"):
        img = bpy.data.images.load(temp_file_path)
        print(f"Imported texture: {img.name}")
        
        # Apply
        material = bpy.data.materials.get(material_name)
        if material:
            apply_texture_to_material(material, img)
    else:
        print(f"File type of {temp_file_path} is not supported for import into Blender.")

def apply_texture_to_material(material, texture):
    # Enable 'Use Nodes' for the material
    material.use_nodes = True
    bsdf = material.node_tree.nodes.get('Principled BSDF')
    
    if not bsdf:
        bsdf = material.node_tree.nodes.new('ShaderNodeBsdfPrincipled')

    # Create a new texture node
    tex_image = material.node_tree.nodes.new('ShaderNodeTexImage')
    tex_image.image = texture

    # Link the texture
    material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color'])

def search_and_apply_texture_to_material(base_path, material_name):
    # Check first the base path
    result = search_in_base_path(base_path, material_name)
    
    # If not found using .pk4 files instead
    if not result:
        pk4_paths = [os.path.join(base_path, pk4) for pk4 in pk4_files]
        preferred_file = search_in_pk4_files(pk4_paths, material_name)
        if preferred_file:
            # Extract and import texture
            with zipfile.ZipFile(preferred_file, 'r') as pk4:
                extract_and_import_texture(pk4, preferred_file, material_name)
    
    print("Search and application process completed.")

# Example usage:
#base_path = "/Q4/q4base/"
#material_name = "textures/example_material"
#search_and_apply_texture_to_material(base_path, material_name)

Script 2: Q4TextureMaterials.py
After the map is imported it looks over the material names and searches for the respective textures

import zipfile
import os
import bpy

# Base path and .pk4 files
base_path = "/Q4/q4base/"
pk4_files = [
    "pak010.pk4", "pak011.pk4", "pak012.pk4",
    "pak013.pk4", "pak014.pk4", "pak016.pk4", "pak019.pk4"
]

# Preferred file type
preferred_extension = ".tga"

def search_in_base_path(base_path, search_path):
    full_path = os.path.join(base_path, search_path)
    if os.path.exists(full_path):
        print(f"Found in base path: {full_path}")
        return [full_path]
    return None

def search_in_pk4_files(pk4_files, search_path):
    matches = []
    for pk4_file in pk4_files:
        with zipfile.ZipFile(pk4_file, 'r') as pk4:
            file_list = pk4.namelist()
            matches.extend([file for file in file_list if file.startswith(search_path)])
            if matches:
                print(f"Found matches in {pk4_file}:")
                for match in matches:
                    print(match)
                # Check preferred file type
                preferred_file = next((file for file in matches if file.endswith(preferred_extension)), None)
                if preferred_file:
                    print(f"Preferred file found: {preferred_file}")
                    extract_and_import_texture(pk4, preferred_file, search_path)
                return matches
    return matches

def extract_and_import_texture(pk4, file_path, material_name):
    temp_dir = bpy.app.tempdir
    temp_file_path = os.path.join(temp_dir, os.path.basename(file_path))
    with pk4.open(file_path) as source, open(temp_file_path, 'wb') as target:
        target.write(source.read())
    
    # Import
    if temp_file_path.endswith(".tga") or temp_file_path.endswith(".dds"):
        img = bpy.data.images.load(temp_file_path)
        print(f"Imported texture: {img.name}")
        
        # Apply
        material = bpy.data.materials.get(material_name)
        if material:
            apply_texture_to_material(material, img)
    else:
        print(f"File type of {temp_file_path} is not supported for import into Blender.")

def apply_texture_to_material(material, texture):
    # Enable 'Use Nodes' for the material
    material.use_nodes = True
    bsdf = material.node_tree.nodes.get('Principled BSDF')
    
    if not bsdf:
        bsdf = material.node_tree.nodes.new('ShaderNodeBsdfPrincipled')

    # Create a new texture node
    tex_image = material.node_tree.nodes.new('ShaderNodeTexImage')
    tex_image.image = texture

    # Link the texture node to the BSDF node
    material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color'])

def main():
    # Seach all materials
    for material in bpy.data.materials:
        if material.name.startswith("textures"):
            search_path = material.name
            print(f"Searching for material: {search_path}")
            
            # Check first the base path
            result = search_in_base_path(base_path, search_path)
            
            # If not found using .pk4 files instead
            if not result:
                pk4_paths = [os.path.join(base_path, pk4) for pk4 in pk4_files]
                result = search_in_pk4_files(pk4_paths, search_path)
    
    print("Search and application process completed.")

# Run the main function
main()

Some notes, not necessarily for you, but also for people potentially stumbling upon this:

  • Certain UV formats (Legacy Quake & Valve-220) depend on the image size for scaling, so doing image search after the import is too late. Doom 3 & Quake 4 use a format that doesn't depend on resolution ("brush primitives"), but some editors may allow mixing & matching, so it's not a guarantee.
  • Despite the name, io_import_wad2 can also import loose images as materials, and name them appropriately for Q3/D3/Q4. This wasn't the focus, so no folder recursion or pk3/zip support, but should be easy enough to adapt. Or just unpack stuff and import one folder at a time.
  • Strictly speaking, adding materials for image files isn't enough. Material names don't have to match image filenames, and Q4 in particular even has an additional template system on top of D3 materials. Full support would involve parsing .shader files (Q3), .mtr files (D3), binary CoD files or who knows what else - which is why this will likely remain a TODO in the importer.
  • Some options in the import dialog are currently misleading (not implemented). Patches are ignored, so be aware that you're probably losing some geometry. Adding support for patches as nurbs and entities as empties/lights should be pretty easy. Might do it over the weekend, though no promises.