blender customization
This commit is contained in:
1 parent
fb39e290c0
commit
8c0a639fba
12 files changed
+263
-7
No files matched your search
@@ -3,11 +3,12 @@ from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
from .paths import GENERATED, PROJECT, slug
|
||||
|
||||
|
||||
def import_model(filename, name="Character"):
|
||||
source = Path(filename).resolve()
|
||||
source = Path(filename).expanduser()
|
||||
source = (source if source.is_absolute() else PROJECT / source).resolve()
|
||||
if not source.is_file():
|
||||
raise FileNotFoundError(f"Brak modelu: {source}")
|
||||
if source.suffix.lower() not in {".glb", ".gltf", ".fbx", ".obj"}:
|
||||
@@ -31,6 +32,10 @@ def import_model(filename, name="Character"):
|
||||
pipeline = unreal.AssetToolsHelpers.get_asset_tools().duplicate_asset(
|
||||
"GLTFWorkshop", GENERATED + "/Pipelines", template)
|
||||
pipeline.set_editor_property("reimport_strategy", unreal.ReimportStrategyFlags.APPLY_PIPELINE_PROPERTIES)
|
||||
pipeline.common_meshes_properties.set_editor_property(
|
||||
"force_all_mesh_as_type", unreal.InterchangeForceMeshType.IFMT_STATIC_MESH)
|
||||
pipeline.mesh_pipeline.set_editor_property("combine_static_meshes", True)
|
||||
pipeline.mesh_pipeline.set_editor_property("import_skeletal_meshes", False)
|
||||
pipeline.material_pipeline.set_editor_property("import_materials", True)
|
||||
# PBR images are imported explicitly into WorkshopTextures below. This
|
||||
# avoids Interchange's dependency-preserving material reimport behavior.
|
||||
@@ -47,9 +52,11 @@ def import_model(filename, name="Character"):
|
||||
unreal.SoftObjectPath(pipeline.get_path_name()),
|
||||
unreal.SoftObjectPath("/Interchange/Pipelines/DefaultGLTFPipeline.DefaultGLTFPipeline")])
|
||||
task.set_editor_property("options", options)
|
||||
if source.suffix.lower() == ".fbx":
|
||||
if source.suffix.lower() in {".fbx", ".obj"}:
|
||||
options = unreal.FbxImportUI()
|
||||
options.set_editor_property("import_mesh", True)
|
||||
options.set_editor_property("import_as_skeletal", False)
|
||||
options.set_editor_property("import_animations", False)
|
||||
options.set_editor_property("mesh_type_to_import", unreal.FBXImportType.FBXIT_STATIC_MESH)
|
||||
options.set_editor_property("automated_import_should_detect_type", False)
|
||||
options.static_mesh_import_data.set_editor_property("combine_meshes", True)
|
||||
@@ -59,10 +66,23 @@ def import_model(filename, name="Character"):
|
||||
if len(meshes) != 1:
|
||||
raise RuntimeError(
|
||||
f"Import {source.name}: oczekiwano jednego StaticMesh, otrzymano {len(meshes)}. "
|
||||
"Polacz obiekty przed eksportem albo uzyj workshop.blender.import_character().")
|
||||
"Plik musi zawierac statyczna geometrie modelu.")
|
||||
if source.suffix.lower() in {".glb", ".gltf"}:
|
||||
# UE preserves material edits on mesh reimport. In this workflow Blender
|
||||
# owns the look; explicitly synchronize the supported PBR parameters.
|
||||
from ._gltf import link_textures
|
||||
link_textures(source, meshes[0], destination)
|
||||
else:
|
||||
# Optional single-atlas convention, also used by the bundled FBX demos.
|
||||
files = {file.stem.lower(): file for file in source.parent.iterdir()
|
||||
if file.suffix.lower() in {".png", ".jpg", ".jpeg"}}
|
||||
maps = {parameter: files.get(source.stem.lower() + suffix)
|
||||
for suffix, parameter in (("_bc", "base_color"), ("_n", "normal"),
|
||||
("_r", "roughness"), ("_m", "metallic"), ("_e", "emissive"))}
|
||||
if maps["base_color"] is not None:
|
||||
from .materials import texture_set
|
||||
material = texture_set(name + "Imported", **maps)
|
||||
for index in range(len(meshes[0].static_materials)):
|
||||
meshes[0].set_material(index, material)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(meshes[0])
|
||||
return meshes[0]
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Small opaque PBR materials, generated without external assets."""
|
||||
import unreal
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
|
||||
|
||||
@@ -32,3 +34,58 @@ def assign(actor, material, slot=0):
|
||||
if component is None:
|
||||
raise TypeError("Material mozna przypisac do aktora ze StaticMeshComponent.")
|
||||
component.set_material(slot, material)
|
||||
|
||||
|
||||
def texture_set(name, *, base_color, normal=None, roughness=None, metallic=None, emissive=None):
|
||||
"""Create one atlas material from adjacent PBR textures (PNG/JPEG)."""
|
||||
slug(name)
|
||||
tools = unreal.AssetToolsHelpers.get_asset_tools()
|
||||
path = f"{GENERATED}/Materials/M_{name}"
|
||||
material = unreal.load_asset(path)
|
||||
if material is None:
|
||||
material = tools.create_asset("M_" + name, GENERATED + "/Materials",
|
||||
unreal.Material, unreal.MaterialFactoryNew())
|
||||
lib = unreal.MaterialEditingLibrary
|
||||
lib.delete_all_material_expressions(material)
|
||||
entries = [("BaseColor", base_color, unreal.MaterialProperty.MP_BASE_COLOR, "RGB", "color"),
|
||||
("Normal", normal, unreal.MaterialProperty.MP_NORMAL, "RGB", "normal"),
|
||||
("Roughness", roughness, unreal.MaterialProperty.MP_ROUGHNESS, "R", "linear"),
|
||||
("Metallic", metallic, unreal.MaterialProperty.MP_METALLIC, "R", "linear"),
|
||||
("Emissive", emissive, unreal.MaterialProperty.MP_EMISSIVE_COLOR, "RGB", "color")]
|
||||
for index, (label, filename, prop, channel, role) in enumerate(entries):
|
||||
if filename is None:
|
||||
if label in {"Roughness", "Metallic"}:
|
||||
node = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -300, index * 180)
|
||||
node.set_editor_property("r", 0.6 if label == "Roughness" else 0.0)
|
||||
lib.connect_material_property(node, "", prop)
|
||||
continue
|
||||
source = Path(filename).resolve()
|
||||
if not source.is_file():
|
||||
raise FileNotFoundError(source)
|
||||
task = unreal.AssetImportTask()
|
||||
task.filename = str(source)
|
||||
task.destination_path = f"{GENERATED}/Materials/Textures/{name}"
|
||||
task.destination_name = "T_" + label
|
||||
task.automated = True
|
||||
task.replace_existing = True
|
||||
task.save = True
|
||||
task.set_editor_property("async_", False)
|
||||
tools.import_asset_tasks([task])
|
||||
textures = [obj for obj in task.get_objects() if isinstance(obj, unreal.Texture2D)]
|
||||
if len(textures) != 1:
|
||||
raise RuntimeError("Nie mozna zaimportowac tekstury " + str(source))
|
||||
texture = textures[0]
|
||||
texture.set_editor_property("srgb", role == "color")
|
||||
texture.set_editor_property("compression_settings", unreal.TextureCompressionSettings.TC_NORMALMAP
|
||||
if role == "normal" else unreal.TextureCompressionSettings.TC_MASKS
|
||||
if role == "linear" else unreal.TextureCompressionSettings.TC_DEFAULT)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(texture)
|
||||
node = lib.create_material_expression(material, unreal.MaterialExpressionTextureSample, -300, index * 180)
|
||||
node.set_editor_property("texture", texture)
|
||||
node.set_editor_property("sampler_type", unreal.MaterialSamplerType.SAMPLERTYPE_NORMAL
|
||||
if role == "normal" else unreal.MaterialSamplerType.SAMPLERTYPE_MASKS
|
||||
if role == "linear" else unreal.MaterialSamplerType.SAMPLERTYPE_COLOR)
|
||||
lib.connect_material_property(node, channel, prop)
|
||||
lib.recompile_material(material)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(material)
|
||||
return material
|
||||
@@ -16,6 +16,12 @@ def slug(value):
|
||||
|
||||
def executable(name, explicit=None):
|
||||
candidate = explicit or os.environ.get("WORKSHOP_" + name.upper()) or shutil.which(name)
|
||||
if not candidate and name == "blender" and os.name == "nt":
|
||||
# Standard Windows installs do not necessarily add Blender to PATH.
|
||||
root = Path(os.environ.get("PROGRAMFILES", r"C:\Program Files")) / "Blender Foundation"
|
||||
installed = [path for path in root.glob("Blender */blender.exe") if path.is_file()]
|
||||
if installed:
|
||||
candidate = max(installed, key=lambda path: tuple(int(part) for part in re.findall(r"\d+", path.parent.name)))
|
||||
if candidate and Path(candidate).is_file():
|
||||
return str(Path(candidate).resolve())
|
||||
raise FileNotFoundError(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""An isolated generated map, simple meshes and a ready-to-use character."""
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
from .paths import GENERATED, PROJECT, slug
|
||||
|
||||
|
||||
class Scene:
|
||||
@@ -65,6 +68,41 @@ class Scene:
|
||||
raise ValueError("Ksztalt: Cube, Sphere, Cylinder, Cone lub Plane.")
|
||||
return self.mesh(f"/Engine/BasicShapes/{shape}.{shape}", **kwargs)
|
||||
|
||||
def model(self, filename, name="Character", height=200, rotation=(0, 0, 0)):
|
||||
"""Import a model, fit its height (cm), and give it a floor-centered pivot.
|
||||
|
||||
Returns a parent actor for transform animation; imported materials stay
|
||||
on its mesh child. Rotation is (roll, pitch, yaw), applied before fitting.
|
||||
"""
|
||||
if height is not None and (not isinstance(height, (int, float)) or
|
||||
not math.isfinite(height) or height <= 0):
|
||||
raise ValueError("Wysokosc modelu musi byc dodatnia; None zachowuje rozmiar pliku.")
|
||||
source = Path(filename).expanduser()
|
||||
source = source if source.is_absolute() else PROJECT / source
|
||||
if source.suffix.lower() == ".blend":
|
||||
from .blender import import_character
|
||||
mesh = import_character(source, name=name)
|
||||
else:
|
||||
from .assets import import_model
|
||||
mesh = import_model(source, name=name)
|
||||
actor = self.mesh(mesh, name=name + "Mesh")
|
||||
roll, pitch, yaw = rotation
|
||||
actor.set_actor_rotation(unreal.Rotator(roll=roll, pitch=pitch, yaw=yaw), False)
|
||||
origin, extent = actor.get_actor_bounds(False)
|
||||
if extent.z <= 0:
|
||||
self.subsystem.destroy_actor(actor)
|
||||
raise ValueError("Model ma zerowa wysokosc. Sprawdz geometrie i MODEL_ROTATION.")
|
||||
if height is not None:
|
||||
factor = float(height) / (2 * extent.z)
|
||||
actor.set_actor_scale3d(unreal.Vector(factor, factor, factor))
|
||||
origin, extent = actor.get_actor_bounds(False)
|
||||
actor.set_actor_location(unreal.Vector(-origin.x, -origin.y, extent.z - origin.z), False, False)
|
||||
root = self.spawn(unreal.StaticMeshActor, name)
|
||||
root.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
actor.attach_to_actor(root, "", unreal.AttachmentRule.KEEP_WORLD,
|
||||
unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False)
|
||||
return root
|
||||
|
||||
def character(self, material=None, head_scale=1.0):
|
||||
root = self.spawn(unreal.StaticMeshActor, "Character")
|
||||
root.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
|
||||
@@ -128,8 +128,64 @@ python3 tools/run_workshop.py --check --blender
|
||||
Ścieżki nie są zapisane na sztywno w bibliotece. Launcher ma domyślną lokalizację
|
||||
instalacji UE 5.7 na Windows; na Linuksie korzysta ze zmiennej lub PATH.
|
||||
|
||||
## Wybór postaci i model pobrany z internetu
|
||||
|
||||
W `exercises/my_shot.py` wybierz `MODEL`:
|
||||
|
||||
| Wartość | Postać |
|
||||
|---|---|
|
||||
| `"primitive"` | Dotychczasowa postać z brył, bez importu. |
|
||||
| `"robot"` | RobotPolice z dołączonego `DemoAssets`. |
|
||||
| `"pilot"` | Humanoid z `DemoAssets/pilot`. |
|
||||
| `"file"` | Własny plik wskazany w `MODEL_FILE`. |
|
||||
|
||||
Przykład własnego modelu:
|
||||
|
||||
```python
|
||||
MODEL = "file"
|
||||
MODEL_FILE = PROJECT / "source_assets/models/my_character.glb"
|
||||
MODEL_HEIGHT = 200
|
||||
MODEL_ROTATION = (0, 0, 90)
|
||||
```
|
||||
|
||||
Rozpakuj pobrany model razem z teksturami. Możesz użyć ścieżki absolutnej
|
||||
albo ścieżki względem projektu. GLB z osadzonymi teksturami PNG/JPEG pozwala
|
||||
przenieść podstawowe materiały PBR w jednym pliku. FBX i OBJ wymagają zachowania
|
||||
plików tekstur i powiązanych FBM/MTL. Nie wczytujemy ZIP ani plików `.uasset`.
|
||||
Import zawsze odbywa się do wydzielonego katalogu generowanych assetów.
|
||||
|
||||
Biblioteka łączy części modelu, ustawia jego wysokość w centymetrach i przesuwa
|
||||
podstawę na Z=0. `MODEL_HEIGHT=None` zachowuje rozmiar źródłowy. Jeśli model leży
|
||||
na boku, popraw `MODEL_ROTATION` (roll, pitch, yaw); obrót jest stosowany przed
|
||||
dopasowaniem wysokości. Pivot animacji znajduje się na środku podstawy.
|
||||
|
||||
Dla pojedynczego atlasu FBX/OBJ można dołączyć mapy `NazwaModelu_BC.png`,
|
||||
`_N`, `_R`, `_M`, `_E` (również JPG/JPEG). Biblioteka odtworzy z nich jeden
|
||||
materiał dla wszystkich slotów. Robot i pilot korzystają z tej konwencji.
|
||||
Bez pliku `_BC` pozostają materiały zaimportowane przez UE. W GLB zachowywany
|
||||
jest osobny materiał dla każdego slotu.
|
||||
|
||||
Po zmianie postaci reszta skryptu działa tak samo: klucze `location`, `rotation`
|
||||
i `scale` animują cały model. W przykładzie jest też opcjonalny podskok.
|
||||
Modele z rigami są w tej wersji importowane jako statyczna geometria:
|
||||
animacje kości, chodzenie z Mixamo i retargeting nie są jeszcze obsługiwane.
|
||||
Wybrany model działa zarówno w edytorze, jak i przez `my_shot_headless.py`.
|
||||
|
||||
## Blender → UE → film
|
||||
|
||||
Własną kopię wybierzesz też bezpośrednio w `my_shot.py`:
|
||||
|
||||
```python
|
||||
MODEL = "file"
|
||||
MODEL_FILE = PROJECT / "source_assets/character/character_custom.blend"
|
||||
```
|
||||
|
||||
Zapisz zmiany w Blenderze i ponownie uruchom ćwiczenie. Za każdym razem plik
|
||||
jest eksportowany w tle i aktualizuje ten sam asset `CustomCharacter` w UE.
|
||||
Nie trzeba ręcznie usuwać poprzedniego modelu. Eksport obejmuje kolekcję `Character`.
|
||||
Na Windows biblioteka wykrywa też standardowe instalacje w `Blender Foundation`,
|
||||
jeśli Blender nie jest w PATH; `WORKSHOP_BLENDER` pozwala wybrać inną instalację.
|
||||
|
||||
Przykładowy model znajduje się w `source_assets/character/character.blend`.
|
||||
Można go też odtworzyć (generator nie nadpisuje istniejącego pliku):
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ Import: `from ue_workshop import Scene, Sequence, Renderer, camera, lighting, ma
|
||||
| Import statycznego modelu | `mesh = assets.import_model("C:/models/model.glb", name="MyModel")` |
|
||||
| Eksport + import Blendera | `mesh = blender.import_character("C:/models/character.blend")` |
|
||||
| Model na scenie | `actor = scene.mesh(mesh, name="Hero")` |
|
||||
| Import z dopasowaniem rozmiaru i pivotu | `actor = scene.model("source_assets/models/hero.glb", name="Hero", height=200)` |
|
||||
| Korekta osi modelu przed dopasowaniem | `actor = scene.model("hero.fbx", rotation=(0, 0, 90))` |
|
||||
| Światła studyjne | `lights = lighting.studio(scene)` |
|
||||
| Pojedyncze światło | `lighting.area(scene, location=(200, -200, 350), intensity=10000, color=(1, 0.5, 0.2))` |
|
||||
| Kamera | `cam = camera.create(scene, location=(450, -550, 300), target=(0, 0, 100), focal_length=45)` |
|
||||
@@ -35,5 +37,9 @@ Cały obrót: klucze 0 i 360, nie 0 i 0. Kamera i modele to oryginalne obiekty U
|
||||
możesz wywoływać ich metody, np. `actor.set_actor_label("MyHero")`.
|
||||
`shot.asset` to oryginalny LevelSequence, `shot.bind(actor)` zwraca binding UE.
|
||||
|
||||
W `my_shot.py`: `MODEL="robot"` lub `MODEL="pilot"` wybiera dołączony model.
|
||||
`MODEL="file"` wczytuje `MODEL_FILE`. Obiekt z `scene.model()` ma pivot na podstawie
|
||||
i zachowuje materiały; animacja dotyczy całego modelu, nie jego kości.
|
||||
|
||||
Przy pojedynczym podglądzie nie uruchamiaj dodatkowo pełnego renderu:
|
||||
jednocześnie może pracować tylko jeden renderer warsztatowy.
|
||||
@@ -27,6 +27,13 @@ Nie uruchamiano testów na Linuksie ani na Blenderze 4.x.
|
||||
StaticMesh pod tą samą ścieżką po zmianie geometrii.
|
||||
- Zmiana koloru materiału oraz dodanie osadzonej tekstury: sprawdzone wartości
|
||||
parametrów UE, przypisanie tekstury i render do MP4.
|
||||
- Import lokalnych RobotPolice i pilot z FBX, automatyczne dopasowanie wysokości
|
||||
do 200 cm i ustawienie podstawy na Z=0. Odtworzenie materiałów z tekstur PBR.
|
||||
- Pełny `my_shot.py` z robotem: 48 klatek 1920×1080, MP4 i poprawne zamknięcie UE.
|
||||
- GLB po włączeniu łączenia części modelu: zaliczony test eksportu/importu
|
||||
Blendera, aktualizacji materiałów i tekstury oraz renderu.
|
||||
- `character_custom.blend`: dwukrotne wykonanie `my_shot.py`, dwa eksporty
|
||||
Blendera do GLB i aktualizacja tego samego assetu UE; podgląd renderu zaliczony.
|
||||
|
||||
Uruchamianie sprawdzeń jest opisane w README. Wyniki każdego renderu są zapisane
|
||||
w jego `result.json`; logi UE znajdują się w `Saved/Logs`.
|
||||
|
||||
+23
-1
@@ -17,12 +17,30 @@ if "ue_workshop" in sys.modules:
|
||||
importlib.reload(sys.modules["ue_workshop"])
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting, materials
|
||||
from ue_workshop.paths import PROJECT
|
||||
|
||||
# WYBOR POSTACI: "primitive", "robot", "pilot" lub "file".
|
||||
MODEL = "file"
|
||||
MODEL_FILE = PROJECT / "source_assets/character/character_custom.blend"
|
||||
MODEL_HEIGHT = 200 # centymetry; None zachowuje oryginalny rozmiar
|
||||
MODEL_ROTATION = (0, 0, 0) # roll, pitch, yaw; np. (0, 0, 90) obraca model
|
||||
|
||||
# 1. MODEL I SCENA. Jednostki: centymetry. Os Z wskazuje gore.
|
||||
scene = Scene("MyShot")
|
||||
blue = materials.color("Character", rgb=(0.04, 0.35, 0.8), metallic=0.2)
|
||||
floor = materials.color("Floor", rgb=(0.07, 0.08, 0.11))
|
||||
if MODEL == "primitive":
|
||||
blue = materials.color("Character", rgb=(0.04, 0.35, 0.8), metallic=0.2)
|
||||
character = scene.character(material=blue, head_scale=1.0)
|
||||
else:
|
||||
sources = {
|
||||
"robot": (PROJECT / "DemoAssets/RobotPolice/RobotPolice.fbx", "RobotPolice"),
|
||||
"pilot": (PROJECT / "DemoAssets/pilot/pilot.fbx", "Pilot"),
|
||||
"file": (MODEL_FILE, "CustomCharacter"),
|
||||
}
|
||||
if MODEL not in sources:
|
||||
raise ValueError("MODEL: primitive, robot, pilot lub file.")
|
||||
source, name = sources[MODEL]
|
||||
character = scene.model(source, name=name, height=MODEL_HEIGHT, rotation=MODEL_ROTATION)
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2), material=floor)
|
||||
lighting.studio(scene, key_color=(1.0, 0.75, 0.5), fill_color=(0.3, 0.6, 1.0))
|
||||
cam = camera.create(scene, location=(450, -550, 300), focal_length=45)
|
||||
@@ -33,6 +51,10 @@ shot.camera(cam)
|
||||
# Rotacja: (roll, pitch, yaw), w stopniach.
|
||||
shot.transform_key(character, 0, rotation=(0, 0, 0))
|
||||
shot.transform_key(character, 47, rotation=(0, 0, 360))
|
||||
# Opcjonalny podskok (dziala tak samo dla robota i wlasnego modelu):
|
||||
# shot.transform_key(character, 0, location=(0, 0, 0), interpolation="auto")
|
||||
# shot.transform_key(character, 24, location=(0, 0, 80), interpolation="auto")
|
||||
# shot.transform_key(character, 47, location=(0, 0, 0), interpolation="auto")
|
||||
shot.save()
|
||||
|
||||
# 3. WYNIK. Najpierw obejrzyj animacje w Sequencerze, potem zmien RENDER na True.
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
Tutaj możesz wrzucić rozpakowany model i jego tekstury.
|
||||
|
||||
W `exercises/my_shot.py` ustaw `MODEL = "file"`, a w `MODEL_FILE` wskaż plik.
|
||||
Ścieżka może być względem projektu albo absolutna, np. `"D:/Modele/hero.glb"`.
|
||||
Najwygodniejszy jest GLB z osadzonymi teksturami PBR PNG/JPEG.
|
||||
FBX i OBJ wymagają zachowania plików tekstur oraz powiązanych plików MTL/FBM.
|
||||
ZIP trzeba wcześniej rozpakować. Plik `.blend` wymaga zainstalowanego Blendera
|
||||
i kolekcji `Character`, zgodnie z przykładem w README projektu.
|
||||
|
||||
Model jest importowany jako geometria statyczna. Można animować jego pozycję,
|
||||
obrót i skalę. Rig, retargeting i animacje chodzenia nie są obecnie importowane.
|
||||
Tekstury i nazwy materiałów powinny być zachowane w eksporcie; obsługujemy
|
||||
podstawowe PBR, a nie dowolne shadery konkretnego programu.
|
||||
@@ -66,7 +66,7 @@ def main(argv=None):
|
||||
command = [editor, str(ROOT / "ExampleProject.uproject"),
|
||||
"-ExecutePythonScript=" + (ROOT / "tools/ue_bootstrap.py").as_posix(),
|
||||
"-RenderOffscreen", "-unattended", "-nosplash", "-nosound", "-stdout",
|
||||
"-FullStdOutLogOutput", "-NoLoadingScreen"]
|
||||
"-FullStdOutLogOutput", "-NoLoadingScreen", "-nocef"]
|
||||
if args.verbose:
|
||||
# UE writes its own log when console output is streamed directly.
|
||||
command.append("-abslog=" + log_path.as_posix())
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Check local FBX characters, automatic fitting, stable pivots and MRQ."""
|
||||
import unreal
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting
|
||||
from ue_workshop.paths import PROJECT
|
||||
|
||||
scene = Scene("ImportedModelsSmoke")
|
||||
characters = []
|
||||
for name, file in (("RobotSmoke", "DemoAssets/RobotPolice/RobotPolice.fbx"),
|
||||
("PilotSmoke", "DemoAssets/pilot/pilot.fbx")):
|
||||
root = scene.model(PROJECT / file, name=name, height=200)
|
||||
child = root.get_attached_actors()[0]
|
||||
origin, extent = child.get_actor_bounds(False)
|
||||
assert abs(2 * extent.z - 200) < 0.1, (name, origin, extent)
|
||||
assert abs(origin.z - extent.z) < 0.1, (name, origin, extent)
|
||||
assert abs(origin.x) < 0.1 and abs(origin.y) < 0.1
|
||||
mesh = child.static_mesh_component.static_mesh
|
||||
assert len(mesh.static_materials) > 0
|
||||
assert all(slot.material_interface for slot in mesh.static_materials)
|
||||
assert all(slot.material_interface.get_name() == "M_" + name + "Imported" for slot in mesh.static_materials)
|
||||
characters.append(root)
|
||||
characters[1].set_actor_location(unreal.Vector(0, 160, 0), False, False)
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene, location=(600, -850, 400), target=(0, 70, 100))
|
||||
shot = Sequence(scene, frames=2)
|
||||
shot.camera(cam)
|
||||
for character in characters:
|
||||
shot.transform_key(character, 0, rotation=(0, 0, 0))
|
||||
shot.transform_key(character, 1, rotation=(0, 0, 45))
|
||||
Renderer(width=640, height=360, samples=2, warmup=8).render(shot)
|
||||
Reference in new issue
Block a user