Skip to content

Commit

Permalink
Merge pull request #450 from Moo-Ack-Productions/424-migrate-unittests
Browse files Browse the repository at this point in the history
Migrated initial prep material tests, all currently passing.
  • Loading branch information
TheDuckCow committed Aug 14, 2023
2 parents d0a5c76 + 3ee90c4 commit 5721a12
Show file tree
Hide file tree
Showing 27 changed files with 1,124 additions and 786 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ As a quick start:
# Highly recommended, create a local virtual environment (could also define globally)
python3 -m pip install --user virtualenv


python3 -m pip install --upgrade pip # Install/upgrade pip
python3 -m venv ./venv # Add a local virtual env called `venv`

Expand All @@ -67,6 +66,8 @@ source venv/bin/activate
# Now with the env active, do the pip install (or upgrade)
pip install --upgrade bpy-addon-build

# Finally, you can compile MCprep using:
bpy-addon-build --during-build dev # Use dev to use non-prod related resources and tracking.
```

Moving forward, you can now build the addon for all intended supported versions using: `bpy-addon-build -b dev`
Expand Down
20 changes: 12 additions & 8 deletions MCprep_addon/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def __init__(self):
# -----------------------------------------------------------------------------

def icons_init(self):
self.clear_previews()

collection_sets = [
"main", "skins", "mobs", "entities", "blocks", "items", "effects", "materials"]

Expand Down Expand Up @@ -178,6 +180,15 @@ def icons_init(self):
for iconset in collection_sets:
self.preview_collections[iconset] = ""

def clear_previews(self):
for pcoll in self.preview_collections.values():
try:
bpy.utils.previews.remove(pcoll)
except Exception as e:
self.log('Issue clearing preview set ' + str(pcoll))
print(e)
self.preview_collections.clear()

def log(self, statement: str, vv_only: bool = False):
if self.verbose and vv_only and self.very_verbose:
print(statement)
Expand Down Expand Up @@ -398,14 +409,7 @@ def register():


def unregister():
if env.use_icons:
for pcoll in env.preview_collections.values():
try:
bpy.utils.previews.remove(pcoll)
except:
env.log('Issue clearing preview set ' + str(pcoll))
env.preview_collections.clear()

env.clear_previews()
env.json_data = None # actively clearing out json data for next open

env.loaded_all_spawners = False
Expand Down
88 changes: 48 additions & 40 deletions MCprep_addon/materials/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,74 +251,81 @@ def checklist(matName: str, listName: str) -> bool:
return True
return False

# Dataclass representing all options

# Dataclass representing all options
# for prep materials
#
# We use __slots__ since __slots__ prevents the
#
# We use __slots__ since __slots__ prevents the
# following bug:
# p = PrepOptions(...)
# p.psses["..."] = "..."
#
# Where a non-existant variable is used. In
# Python, this would create a new variable
# p = PrepOptions(...)
# p.psses["..."] = "..."
#
# Where a non-existant variable is used. In
# Python, this would create a new variable
# "psses" on p. To prevent this, we use __slots__.
#
# In addition, access to objects in __slots__ is
# In addition, access to objects in __slots__ is
# faster then it would be normally
#
# Python dataclasses have native support for __slots__
# in 3.10, but since 2.8 uses 3.7, we have to use
# Python dataclasses have native support for __slots__
# in 3.10, but since 2.8 uses 3.7, we have to use
# __slots__ directly

@dataclass
class PrepOptions:
__slots__ = ("passes",
"use_reflections",
"use_principled",
"only_solid",
"pack_format",
"use_emission_nodes",
"use_emission")
passes: Dict[str, str]
"""Class defining structure for prepping or generating materials
passes: dictionary struc of all found pass names
use_reflections: whether to turn reflections on
use_principled: if available and cycles, use principled node
saturate: if a desaturated texture (by canonical resource), add color
pack_format: which format of PBR, string ("Simple", Specular", "SEUS")
"""
__slots__ = (
"passes",
"use_reflections",
"use_principled",
"only_solid",
"pack_format",
"use_emission_nodes",
"use_emission")
passes: Dict[str, bpy.types.Image]
use_reflections: bool
use_principled: bool
only_solid: bool
pack_format: str
pack_format: str # TODO: Enforce enum value assignment.
use_emission_nodes: bool
use_emission: bool


def matprep_cycles(mat: Material, options: PrepOptions) -> Optional[bool]:
"""Determine how to prep or generate the cycles materials.
Args:
mat: the existing material
passes: dictionary struc of all found pass names
use_reflections: whether to turn reflections on
use_principled: if available and cycles, use principled node
saturate: if a desaturated texture (by canonical resource), add color
pack_format: which format of PBR, string ("Simple", Specular", "SEUS")
options: All PrepOptions for this configuration, see class definition
Returns:
int: 0 only if successful, otherwise None or other
"""
if util.bv28():
# ensure nodes are enabled esp. after importing from BI scenes
mat.use_nodes = True
# ensure nodes are enabled
mat.use_nodes = True

matGen = util.nameGeneralize(mat.name)
canon, _ = get_mc_canonical_name(matGen)
options.use_emission = checklist(canon, "emit") or "emit" in mat.name.lower()

# TODO: Update different options for water before enabling this
# if use_reflections and checklist(canon, "water"):
# res = matgen_special_water(mat, passes)
# res = matgen_special_water(mat, passes)
# if use_reflections and checklist(canon, "glass"):
# res = matgen_special_glass(mat, passes)
if options.pack_format == "simple" and util.bv28():
# res = matgen_special_glass(mat, passes)
if options.pack_format == "simple":
res = matgen_cycles_simple(mat, options)
elif options.use_principled and hasattr(bpy.types, 'ShaderNodeBsdfPrincipled'):
res = matgen_cycles_principled(mat, options)
elif options.use_principled:
res = matgen_cycles_principled(mat, options)
else:
res = matgen_cycles_original(mat, options)
res = matgen_cycles_original(mat, options)
return res


Expand Down Expand Up @@ -426,6 +433,7 @@ def set_cycles_texture(image: Image, material: Material, extra_passes: bool=Fals

return changed


def get_node_for_pass(material: Material, pass_name: str) -> Optional[Node]:
"""Assumes cycles material, returns texture node for given pass in mat."""
if pass_name not in ["diffuse", "specular", "normal", "displace"]:
Expand Down Expand Up @@ -754,7 +762,7 @@ def create_node(tree_nodes: Nodes, node_type: str, **attrs: Dict[str, Any]) -> N
return node


def get_node_socket(node: Node, is_input: bool=True) -> list:
def get_node_socket(node: Node, is_input: bool = True) -> list:
"""Gets the input or output sockets indicies for node"""
n_type = node.bl_idname
if n_type == 'ShaderNodeMix' or n_type == 'ShaderNodeMixRGB':
Expand Down Expand Up @@ -1192,7 +1200,7 @@ def matgen_cycles_simple(mat: Material, options: PrepOptions) -> Optional[bool]:
mat.blend_method = 'HASHED'
if hasattr(mat, "shadow_method"):
mat.shadow_method = 'HASHED'

if options.use_emission_nodes and options.use_emission:
inputs = [inp.name for inp in principled.inputs]
if 'Emission Strength' in inputs: # Later 2.9 versions only.
Expand All @@ -1212,8 +1220,8 @@ def matgen_cycles_simple(mat: Material, options: PrepOptions) -> Optional[bool]:
else:
env.log(f"Texture desaturated: {canon}", vv_only=True)
desat_color = env.json_data['blocks']['desaturated'][canon]
if len(desat_color) < len(nodeSaturateMix.inputs[2].default_value):
desat_color.append(1.0)
if len(desat_color) < len(nodeSaturateMix.inputs[saturateMixIn[2]].default_value):
desat_color.append(1.0)
nodeSaturateMix.inputs[saturateMixIn[2]].default_value = desat_color
nodeSaturateMix.mute = False
nodeSaturateMix.hide = False
Expand Down Expand Up @@ -1374,15 +1382,15 @@ def matgen_cycles_principled(mat: Material, options: PrepOptions) -> Optional[bo
# both work fine with depth of field.

# but, BLEND does NOT work well with Depth of Field or layering

# reapply animation data if any to generated nodes
apply_texture_animation_pass_settings(mat, animated_data)

return 0


def matgen_cycles_original(mat: Material, options: PrepOptions):
"""Generate principled cycles material"""
"""Generate non-principled cycles material"""

matGen = util.nameGeneralize(mat.name)
canon, form = get_mc_canonical_name(matGen)
Expand Down
43 changes: 22 additions & 21 deletions MCprep_addon/materials/prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ def execute(self, context):
self.report({'ERROR'}, "No materials found on selected objects")
return {'CANCELLED'}


# check if linked material exists
engine = context.scene.render.engine
count = 0
Expand Down Expand Up @@ -248,13 +247,13 @@ def execute(self, context):

if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
options = generate.PrepOptions(
passes,
self.useReflections,
self.usePrincipledShader,
self.makeSolid,
self.packFormat,
self.useEmission,
False # This is for an option set in matprep_cycles
passes=passes,
use_reflections=self.useReflections,
use_principled=self.usePrincipledShader,
only_solid=self.makeSolid,
pack_format=self.packFormat,
use_emission_nodes=self.useEmission,
use_emission=False # This is for an option set in matprep_cycles
)
res = generate.matprep_cycles(
mat=mat,
Expand All @@ -270,26 +269,26 @@ def execute(self, context):

if self.animateTextures:
sequences.animate_single_material(
mat, context.scene.render.engine)
mat,
context.scene.render.engine,
export_location="original")

# Sync materials.
if self.syncMaterials is True:
bpy.ops.mcprep.sync_materials(
selected=True, link=False, replace_materials=False, skipUsage=True)


# Combine materials.
if self.combineMaterials is True:
bpy.ops.mcprep.combine_materials(selection_only=True, skipUsage=True)

# Improve UI.
# Improve UI.
if self.improveUiSettings:
try:
bpy.ops.mcprep.improve_ui()
except RuntimeError as err:
print(f"Failed to improve UI with error: {err}")


if self.optimizeScene and engine == 'CYCLES':
bpy.ops.mcprep.optimize_scene()

Expand Down Expand Up @@ -484,7 +483,9 @@ def execute(self, context):
res += generate.set_texture_pack(mat, folder, self.useExtraMaps)
if self.animateTextures:
sequences.animate_single_material(
mat, context.scene.render.engine)
mat,
context.scene.render.engine,
export_location="original")
# may be a double call if was animated tex
generate.set_saturation_material(mat)

Expand Down Expand Up @@ -642,13 +643,13 @@ def update_material(self, context, mat):

if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
options = generate.PrepOptions(
passes,
self.useReflections,
self.usePrincipledShader,
self.makeSolid,
self.packFormat,
self.useEmission,
False # This is for an option set in matprep_cycles
passes=passes,
use_reflections=self.useReflections,
use_principled=self.usePrincipledShader,
only_solid=self.makeSolid,
pack_format=self.packFormat,
use_emission_nodes=self.useEmission,
use_emission=False # This is for an option set in matprep_cycles
)
res = generate.matprep_cycles(
mat=mat,
Expand All @@ -661,7 +662,7 @@ def update_material(self, context, mat):

if self.animateTextures:
sequences.animate_single_material(
mat, context.scene.render.engine)
mat, context.scene.render.engine, export_location="original")

return success, None

Expand Down
12 changes: 7 additions & 5 deletions MCprep_addon/materials/skin.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def handler_skins_load(scene):

def loadSkinFile(self, context: Context, filepath: Path, new_material: bool=False):
if not os.path.isfile(filepath):
self.report({'ERROR'}, "Image file not found")
self.report({'ERROR'}, f"Image file not found: {filepath}")
return 1
# special message for library linking?

Expand Down Expand Up @@ -469,9 +469,11 @@ def execute(self, context):
self.report({"ERROR"}, "Invalid username")
return {'CANCELLED'}

user_ref = self.username.lower() + ".png"

skins = [str(skin[0]).lower() for skin in env.skin_list]
paths = [skin[1] for skin in env.skin_list]
if self.username.lower() not in skins or not self.skip_redownload:
if user_ref not in skins or not self.skip_redownload:
# Do the download
saveloc = download_user(self, context, self.username)
if not saveloc:
Expand All @@ -485,8 +487,8 @@ def execute(self, context):
return {'FINISHED'}
else:
env.log("Reusing downloaded skin")
ind = skins.index(self.username.lower())
res = loadSkinFile(self, context, paths[ind][1], self.new_material)
ind = skins.index(user_ref)
res = loadSkinFile(self, context, paths[ind], self.new_material)
if res != 0:
return {'CANCELLED'}
return {'FINISHED'}
Expand Down Expand Up @@ -579,7 +581,7 @@ def draw(self, context):
skin_path = env.skin_list[context.scene.mcprep_skins_list_index]
col = self.layout.column()
col.scale_y = 0.7
col.label(text= f"Warning, will delete file {os.path.basename(skin_path[0])} from")
col.label(text=f"Warning, will delete file {os.path.basename(skin_path[0])} from")
col.label(text=os.path.dirname(skin_path[-1]))

@tracking.report_error
Expand Down
3 changes: 2 additions & 1 deletion MCprep_addon/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ def obj_copy(base: bpy.types.Object, context: Optional[Context]=None, vertex_gro
setattr(dest, prop, getattr(mod_src, prop))
return new_ob

def min_bv(version: Tuple, *, inclusive: bool=True) -> bool:

def min_bv(version: Tuple, *, inclusive: bool = True) -> bool:
if hasattr(bpy.app, "version"):
if inclusive is False:
return bpy.app.version > version
Expand Down
Loading

0 comments on commit 5721a12

Please sign in to comment.