Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrating more critical tests ahead of release. #487

Merged
merged 13 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
841 changes: 518 additions & 323 deletions MCprep_addon/MCprep_resources/mcprep_data_update.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion MCprep_addon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
bl_info = {
"name": "MCprep",
"category": "Object",
"version": (3, 4, 3),
"version": (3, 5, 0),
"blender": (2, 80, 0),
"location": "3D window toolshelf > MCprep tab",
"description": "Minecraft workflow addon for rendering and animation",
Expand Down
15 changes: 11 additions & 4 deletions MCprep_addon/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ def __init__(self):

self.dev_file: Path = Path(os.path.dirname(__file__), "mcprep_dev.txt")

# if new update file found from install, replace old one with new
if self.json_path_update.exists():
self.json_path_update.replace(self.json_path)

self.last_check_for_updated = 0

# Check to see if there's a text file for a dev build. If so,
Expand Down Expand Up @@ -128,6 +124,16 @@ def __init__(self):
# If ever changing the resource pack, should also reset to None.
self.material_sync_cache = []

def update_json_dat_path(self):
"""If new update file found from install, replace old one with new.

Should be called as part of register, as otherwise this renaming will
trigger the renaming of the source file in git history when running
tests.
"""
if self.json_path_update.exists():
self.json_path_update.replace(self.json_path)

# -----------------------------------------------------------------------------
# ICONS INIT
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -406,6 +412,7 @@ def register():
# the addon was disabled once (or more) and then re-enabled, while
# avoiding a double call to init() on the first time load.
env = MCprepEnv()
env.update_json_dat_path()


def unregister():
Expand Down
19 changes: 15 additions & 4 deletions MCprep_addon/spawner/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,22 +435,32 @@ def geo_update_params(context: Context, effect: ListEffectsAssets, geo_mod: Node
else:
center_empty.location = util.get_cursor_location()

input_list = []
input_node = None
for nd in geo_mod.node_group.nodes:
if nd.type == "GROUP_INPUT":
input_node = nd
break
if input_node is None:
raise RuntimeError(f"Geo node has no input group: {effect.name}")
input_list = list(input_node.outputs)

# Cache mapping of names like "Weather Type" to "Input_1" internals.
geo_inp_id = {}
for inp in geo_mod.node_group.inputs:
for inp in input_list:
if inp.name in list(geo_fields):
geo_inp_id[inp.name] = inp.identifier

# Now update the final geo node inputs based gathered settings.
for inp in geo_mod.node_group.inputs:
for inp in input_list:
if inp.name in list(geo_fields):
value = geo_fields[inp.name]
if value == "CAMERA_OBJ":
env.log("Set cam for geonode input", vv_only=True)
geo_mod[geo_inp_id[inp.name]] = camera
elif value == "FOLLOW_OBJ":
if not center_empty:
print(">> Center empty missing, not in preset!")
env.log("Geo Node effects: Center empty missing, not in preset!")
else:
env.log("Set follow for geonode input", vv_only=True)
geo_mod[geo_inp_id[inp.name]] = center_empty
Expand Down Expand Up @@ -491,7 +501,8 @@ def geo_fields_from_json(effect: ListEffectsAssets, jpath: Path) -> dict:
return geo_fields


def get_or_create_plane_mesh(mesh_name: str, uvs: Sequence[Tuple[int,int]]=[]) -> Mesh:
def get_or_create_plane_mesh(
mesh_name: str, uvs: Sequence[Tuple[int, int]] = []) -> Mesh:
"""Generate a 1x1 plane with UVs stretched out to ends, cache if exists.

Arg `uvs` represents the 4 coordinate values clockwise from top left of the
Expand Down
157 changes: 86 additions & 71 deletions MCprep_addon/spawner/mcmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from mathutils import Vector
from math import sin, cos, radians
from pathlib import Path
from typing import Dict, Optional, Tuple, Union, Sequence, List
from typing import Dict, Optional, Tuple, Union, Sequence

import bpy
import bmesh
Expand All @@ -48,7 +48,13 @@ class ModelException(Exception):


def rotate_around(
d: float, pos: VectorType, origin: VectorType, axis:str='z', offset: VectorType=[8, 0, 8], scale: VectorType=[0.0625, 0.0625, 0.0625]) -> VectorType:
d: float,
pos: VectorType,
origin: VectorType,
axis: str = 'z',
offset: VectorType = [8, 0, 8],
scale: VectorType = [0.0625, 0.0625, 0.0625]
) -> VectorType:
r = -radians(d)
axis_i = ord(axis) - 120 # 'x'=0, 'y'=1, 'z'=2
a = pos[(1 + axis_i) % 3]
Expand All @@ -69,56 +75,61 @@ def rotate_around(
(new_pos[2] - offset[2]) * scale[2],
(new_pos[1] - offset[1]) * scale[1]
))



def add_element(
elm_from: VectorType=[0, 0, 0],
elm_to: VectorType=[16, 16, 16],
rot_origin: VectorType=[8, 8, 8],
rot_axis: str='y',
rot_angle: float=0) -> list:
"""Calculates and defines the verts, edge, and faces that to create."""
verts = [
rotate_around(
rot_angle, [elm_from[0], elm_to[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_to[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_from[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_from[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_to[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_to[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_from[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_from[1], elm_to[2]], rot_origin, rot_axis),
]

edges = []
faces = [
[0, 1, 2, 3], # north
[5, 4, 7, 6], # south
[1, 0, 4, 5], # up
[7, 6, 2, 3], # down
[4, 0, 3, 7], # west
[1, 5, 6, 2]] # east

return verts, edges, faces

def add_material(name: str="material", path: str="", use_name: bool= False) -> Optional[Material]:
elm_from: VectorType = [0, 0, 0],
elm_to: VectorType = [16, 16, 16],
rot_origin: VectorType = [8, 8, 8],
rot_axis: str = 'y',
rot_angle: float = 0
) -> list:
"""Calculates and defines the verts, edge, and faces that to create."""
verts = [
rotate_around(
rot_angle, [elm_from[0], elm_to[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_to[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_from[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_from[1], elm_from[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_to[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_to[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_to[0], elm_from[1], elm_to[2]], rot_origin, rot_axis),
rotate_around(
rot_angle, [elm_from[0], elm_from[1], elm_to[2]], rot_origin, rot_axis),
]

edges = []
faces = [
[0, 1, 2, 3], # north
[5, 4, 7, 6], # south
[1, 0, 4, 5], # up
[7, 6, 2, 3], # down
[4, 0, 3, 7], # west
[1, 5, 6, 2]] # east

return verts, edges, faces


def add_material(
name: str = "material", path: str = "", use_name: bool = False
) -> Optional[Material]:
"""Creates a simple material with an image texture from path."""
engine = bpy.context.scene.render.engine

# Create the base material node tree setup
mat, err = generate.generate_base_material(bpy.context, name, path, False)
if mat is None and err:
env.log("Failed to fetch any generated material")
return None

passes = generate.get_textures(mat)
# In most case Minecraft JSON material
# In most case Minecraft JSON material
# do not use PBR passes, so set it to None
for pass_name in passes:
if pass_name != "diffuse":
Expand All @@ -127,26 +138,28 @@ def add_material(name: str="material", path: str="", use_name: bool= False) -> O
# Halt if no diffuse image found
if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
options = generate.PrepOptions(
passes=passes,
use_reflections=False,
use_principled=True,
only_solid=False,
pack_format=generate.PackFormat.SIMPLE,
use_emission_nodes=False,
use_emission=False # This is for an option set in matprep_cycles
passes=passes,
use_reflections=False,
use_principled=True,
only_solid=False,
pack_format=generate.PackFormat.SIMPLE,
use_emission_nodes=False,
use_emission=False # This is for an option set in matprep_cycles
)
res = generate.matprep_cycles(
_ = generate.matprep_cycles(
mat=mat,
options=options
)

if use_name:
mat.name = name

return mat


def locate_image(context: Context, textures: Dict[str, str], img: str, model_filepath: Path) -> Path:
def locate_image(
context: Context, textures: Dict[str, str], img: str, model_filepath: Path
) -> Path:
"""Finds and returns the filepath of the image texture."""
resource_folder = bpy.path.abspath(context.scene.mcprep_texturepack_path)

Expand All @@ -160,7 +173,7 @@ def locate_image(context: Context, textures: Dict[str, str], img: str, model_fil
if local_path[0] == '.': # path is local to the model file
directory = os.path.dirname(model_filepath)
else:
if(len(local_path.split(":")) == 1):
if len(local_path.split(":")) == 1:
namespace = "minecraft"
else:
namespace = local_path.split(":")[0]
Expand All @@ -171,7 +184,8 @@ def locate_image(context: Context, textures: Dict[str, str], img: str, model_fil
return os.path.realpath(os.path.join(directory, local_path) + ".png")


def read_model(context: Context, model_filepath: Path) -> Tuple[Element, Texture]:
def read_model(
context: Context, model_filepath: Path) -> Tuple[Element, Texture]:
"""Reads json file to get textures and elements needed for model.

This function is recursively called to also get the elements and textures
Expand Down Expand Up @@ -215,7 +229,7 @@ def read_model(context: Context, model_filepath: Path) -> Tuple[Element, Texture
# heads, shields, banners and tridents.
pass
else:
if(len(parent.split(":")) == 1):
if len(parent.split(":")) == 1:
namespace = "minecraft"
parent_filepath = parent
else:
Expand Down Expand Up @@ -258,18 +272,20 @@ def read_model(context: Context, model_filepath: Path) -> Tuple[Element, Texture
return elements, textures


def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int, bpy.types.Object]:
def add_model(
model_filepath: Path, obj_name: str = "MinecraftModel"
) -> Tuple[int, bpy.types.Object]:
"""Primary function for generating a model from json file."""
collection = bpy.context.collection
view_layer = bpy.context.view_layer

# Called recursively!
# Can raise ModelException due to permission or corrupted file data.
elements, textures = read_model(bpy.context, model_filepath)

if elements is None:
return 1, None

mesh = bpy.data.meshes.new(obj_name) # add a new mesh
obj = bpy.data.objects.new(obj_name, mesh) # add a new object using the mesh
collection.objects.link(obj) # put the object into the scene (link)
Expand All @@ -283,17 +299,15 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int

materials = []
if textures:
particle = textures.get("particle")
for img in textures:
if img != "particle":
tex_pth = locate_image(bpy.context, textures, img, model_filepath)
mat = add_material(f"{obj_name}_{img}", tex_pth, use_name=False)
obj_mats = obj.data.materials
if not f"#{img}" in materials:
if f"#{img}" not in materials:
obj_mats.append(mat)
materials.append(f"#{img}")


for e in elements:
rotation = e.get("rotation")
if rotation is None:
Expand All @@ -314,7 +328,7 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int
d_face = faces.get(face_dir[i])
if not d_face:
continue

face_mat = d_face.get("texture")
# uv can be rotated 0, 90, 180, or 270 degrees
uv_rot = d_face.get("rotation")
Expand All @@ -327,7 +341,7 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int

uv_coords = d_face.get("uv") # in the format [x1, y1, x2, y2]
if uv_coords is None:
uv_coords = [0, 0, 16, 16]
uv_coords = [0, 0, 16, 16]
# Cake and cake slices don't store the UV keys
# in the JSON model, which causes issues. This
# workaround this fixes those texture issues
Expand All @@ -336,7 +350,7 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int
uv_coords = [e['to'][0], e['to'][2], e['from'][0], e['from'][2]]
if "side" in face_mat:
uv_coords = [e['to'][0], -e['to'][1], e['from'][0], -e['from'][2]]

# uv in the model is between 0 to 16 regardless of resolution,
# in blender its 0 to 1 the y-axis is inverted when compared to
# blender uvs, which is why it is subtracted from 1, essentially
Expand All @@ -347,11 +361,11 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int
[uv_coords[0] / 16, 1 - (uv_coords[3] / 16)], # [x1, y2]
[uv_coords[2] / 16, 1 - (uv_coords[3] / 16)] # [x2, y2]
]

face = bm.faces.new(
(verts[f[0]], verts[f[1]], verts[f[2]], verts[f[3]])
)

face.normal_update()
for j in range(len(face.loops)):
# uv coords order is determened by the rotation of the uv,
Expand All @@ -362,11 +376,10 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> Tuple[int
# Assign the material on face
if face_mat is not None and face_mat in materials:
face.material_index = materials.index(face_mat)
is_first = False

# Quick way to clean the model, hopefully it doesn't cause any UV issues
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01)

# make the bmesh the object's mesh
bm.to_mesh(mesh)
bm.free()
Expand Down Expand Up @@ -439,7 +452,9 @@ def update_model_list(context: Context):
continue
# Filter the "unspawnable_for_now"
# Either entity block or block that doesn't good for json
blocks = env.json_data.get("unspawnable_for_now", ["bed", "chest", "banner", "campfire"])
blocks = env.json_data.get(
"unspawnable_for_now",
["bed", "chest", "banner", "campfire"])
if name in blocks:
continue
item = scn_props.model_list.add()
Expand Down
Loading