Develop Addon with Python
Add dependencies
omoospace requires Python version 3.10 or above.
How to use
Omoospace
from omoospace import create_omoospace, Opath
omoospace = create_omoospace(
"new project",
under="temp",
brief="A new project for testing."
)
# the name conversion rule is:
# 1. convert to "PascalCase"
# 2. remove special characters and spaces
# so the name "new project" is converted to "NewProject"
# "NewProject" is also the project folder name
assert omoospace.name == "NewProject"
assert omoospace.root_dir == Opath("temp", "NewProject")
assert omoospace.subspaces_dir == Opath("temp", "NewProject", "Subspaces")
assert omoospace.contents_dir == Opath("temp", "NewProject", "Contents")
assert omoospace.profile_file == Opath("temp", "NewProject", "Omoospace.yml")
# a empty project is created.
assert omoospace.brief == "A new project for testing."
assert len(omoospace.subspaces) == 0
# %$ is not allowed as a omoospace name.
with pytest.raises(ValueError):
create_omoospace("%$", under="temp")
Example omoospace:
Project01
├── Contents
├── Prop01.blend
├── Prop02
│ ├── 001-ModelProp02.zpr
│ ├── 002-TextureProp02.spp
│ ╰── 003-RenderProp02.blend
├── Prop03
│ ├── Prop03.blend
│ ├── PartA.blend
│ ╰── PartB.blend
╰── Omoospace.yml
from omoospace import Omoospace, Opath
omoospace = Omoospace("ProjectRoot")
assert omoospace.root_dir == Opath("ProjectRoot")
assert omoospace.subspaces_dir == Opath("ProjectRoot")
assert omoospace.contents_dir == Opath("ProjectRoot", "Contents")
assert omoospace.profile_file == Opath("ProjectRoot", "Omoospace.yml")
assert omoospace.contents_dir.is_dir()
assert omoospace.subspaces_dir.is_dir()
assert omoospace.profile_file.is_file()
assert len(omoospace.subspaces) == 9
objective_tree = omoospace.objective_tree
assert objective_tree.count == 8
print(objective_tree.format())
# Project01
# ├── Prop01
# ├── Prop02
# │ ├── 001-ModelProp02
# │ ├── 002-TextureProp02
# │ ╰── 003-RenderProp02
# ╰── Prop03
# ├── PartA.blend
# ╰── PartB.blend
for o in objective_tree:
if o.name == "Prop01":
assert len(o.subspaces) == 1
assert len(o.children) == 0
elif o.name == "Prop02":
assert len(o.subspaces) == 1
assert len(o.children) == 3
elif o.name == "Prop03":
assert len(o.subspaces) == 2
assert len(o.children) == 2
Subspace
Example omoospace:
Project02
├── Contents
├── Subspaces
│ ├── Heart.blend
│ ├── Heart_v001.blend
│ ├── Heart_Valves.blend
│ ├── Heart_Valves_v001_autosave.spp
│ ╰── Liver.zpr
╰── Omoospace.yml
from omoospace import Omoospace, Opath
omoospace = Omoospace('path/to/Project')
make_heart = omoospace.add_subspace(name="heart")
# "add_subspace" will collect subspaces that has same objective.
# All files except "Liver.zpr" will move to Heart/
assert Opath(omoospace.subspaces_dir, "Heart/Heart.blend").exists()
assert Opath(omoospace.subspaces_dir, "Heart/Heart.v001.blend").exists()
assert Opath(omoospace.subspaces_dir, "Heart/Heart_Valves.spp").exists()
assert Opath(omoospace.subspaces_dir, "Heart/Heart_Valves.v001.spp").exists()
assert Opath(omoospace.subspaces_dir, "Liver.zpr").exists()
assert len(make_heart.subspaces) == 5
assert "Heart" in make_heart.subspaces
assert "Heart/Heart.blend" in make_heart.subspaces
assert "Heart/Heart.v001.blend" in make_heart.subspaces
assert "Heart/Heart_Valves.spp" in make_heart.subspaces
assert "Heart/Heart_Valves.v001.spp" in make_heart.subspaces
# Add subspace under heart.
make_valves = omoospace.add_subspace("valves", under=make_heart)
assert make_valves.pathname == "Heart_Valves"
assert len(make_heart.subspaces) == 6
# Heart_Valves.spp not move to Heart/Valves/
assert Opath(omoospace.subspaces_dir, "Heart/Heart_Valves.spp").exists()
assert Opath(omoospace.subspaces_dir, "Heart/Heart_Valves.v001.spp").exists()
from omoospace import extract_pathname
assert extract_pathname("Sc010_Prop01.blend") == "Sc010_Prop01"
assert extract_pathname("SQ010/Prop01.blend") == "Sc010_Prop01"
assert extract_pathname("Sc010_Shot0100/Prop01.blend") == "Sc010_Prop01"
assert extract_pathname("Sc010_Shot0100/Shot0100_Prop01.blend") == "Sc010_Shot0100_Prop01"
assert extract_pathname("Sc010_Shot0100/Sc010_Shot0100_Prop01.blend") == "Sc010_Shot0100_Prop01"
assert extract_pathname("PartA/Prop01_PartA.blend") == "PartA_Prop01_PartA"
assert extract_pathname("Prop01.001.blend") == "Prop01"
assert extract_pathname("Prop01.v001.blend") == "Prop01"
assert extract_pathname("Asset-A.v001.blend") == "Asset-A"
assert extract_pathname("Prop01.v001.autosave.blend") == "Prop01"
assert extract_pathname("头骨/头骨.blend") == "头骨"
assert extract_pathname("Asset A/Prop01.blend") == "Prop01"
assert extract_pathname("Prop01_Prop01.blend") == "Prop01_Prop01"
Maker
# read makers
maker = omoospace.get_maker("马南001")
assert maker.email == "manan001@example.com"
assert maker.website == "https://www.manan.com"
maker = omoospace.get_maker("马南002")
assert maker.email == "manan002@example.com"
assert maker.website == None
maker.website = "https://www.manan2.com"
assert maker.website == "https://www.manan2.com"
assert maker.email == "manan002@example.com"
maker = omoospace.get_maker("偶魔数字")
assert maker.email == None
assert maker.website == "https://www.omoolab.xyz"
# add maker
maker = omoospace.add_maker("icrdr")
assert len(omoospace.makers) == 4
assert "icrdr" in omoospace.makers
assert omoospace.makers == ["马南001", "马南002", "偶魔数字", "icrdr"]
Tool
tool = omoospace.get_tool("Blender")
assert tool.version == "4.2.0"
assert tool.website == "https://www.blender.org"
assert tool.extensions == ["Omoospace", "BioxelNodes"]
tool = omoospace.get_tool("Houdini")
assert tool.version == "20.0.0"
assert tool.website == None
tool.version = "21.0.0"
assert tool.version == "21.0.0"
assert tool.website == None
tool.website = "https://www.houdini.com"
assert tool.version == "21.0.0"
assert tool.website == "https://www.houdini.com"
# edit tool Blender
tool = omoospace.get_tool("Blender")
tool.extensions = ["Omoospace"]
tool.version = ">3.6.5"
assert "BioxelNodes" not in tool.extensions
# get tool by name
assert omoospace.get_tool("Blender").version == ">3.6.5"
# remove tool
omoospace.remove_tool("Blender")
assert len(omoospace.tools) == 2
Work
from omoospace import Omoospace
omoospace = Omoospace('path/to/Project')
work = omoospace.get_work("超厉害动画")
assert work.brief == "一个超厉害的动画."
assert work.version == "1.0.0"
assert work.contents == [
"视频/动画A.mp4",
"图片/动画A_封面.png",
]
assert work.contributions["动画师"] == ["马南003", "马南002"]
assert work.contributions["动画导演"] == ["马南001"]
work = omoospace.get_work("超厉害模型")
assert work.brief == None
assert work.version == None
assert work.contents == ["模型/模型A/模型A.fbx", "模型/模型A/贴图"]
assert len(work.contributions) == 0
work.add_contribution("马南003", contribution="模型师")
assert work.contributions["模型师"] == ["马南003"]
assert work.contents == ["模型/模型A/模型A.fbx", "模型/模型A/贴图"]
work = omoospace.get_work("另一个超厉害模型")
assert work.brief == None
assert work.version == None
assert work.contents == ["模型/模型B.glb"]
assert len(work.contributions) == 0
work.brief = "一个超酷的模型."
assert work.brief == "一个超酷的模型."
assert work.contents == ["模型/模型B.glb"]
# set contributions
work.contributions = {
"模型师": ["马南003", {"name": "马南002", "email": "manan2@example.com"}]
}
assert len(work.contributions["模型师"]) == 2
with pytest.raises(KeyError):
assert len(work.contributions["主创"]) == 0
# delete content will affect work items
Opath(omoospace.contents_dir, "模型").remove()
assert len(work.contents) == 0
# remove work
omoospace.remove_work("另一个超厉害模型")
assert len(omoospace.works) == 2
Opath
root = mini_omoos_path
make_path(
"Prop01.blend",
"Prop02/001-ModelProp02.zpr",
"Prop02/002-TextureProp02.spp",
"Prop02/003-RenderProp02.blend",
"Prop03/Prop03.blend",
"Prop03/PartA.blend",
"Prop03/PartB.blend",
under=root,
)
Opath(root / "Prop01.blend").copy_to(root / "Temp")
assert (root / "Prop01.blend").exists()
assert (root / "Temp" / "Prop01.blend").exists()
# if the same, return self
assert (
Opath(root / "Prop01.blend").copy_to(root, overwrite=True)
== root / "Prop01.blend"
)
# Asset.blend is already in Temp, copy fail
with pytest.raises(FileExistsError):
Opath(root / "Prop01.blend").copy_to(root / "Temp")
# with overwrite=True, Prop01.blend in Temp will be overwritten
Opath(root / "Prop01.blend").copy_to(root / "Temp", overwrite=True)
assert (root / "Temp" / "Prop01.blend").exists()
# Asset.blend is already in Temp, move fail
with pytest.raises(FileExistsError):
Opath(root / "Prop01.blend").move_to(root / "Temp")
# with overwrite=True, Prop01.blend in Temp will be overwritten
Opath(root / "Prop01.blend").move_to(root / "Temp", overwrite=True)
assert (root / "Temp" / "Prop01.blend").exists()
assert not (root / "Prop01.blend").exists()
# copy folder Prop03 to Temp
Opath(root / "Prop03").copy_to(root / "Temp")
assert (root / "Prop03").exists()
assert (root / "Temp" / "Prop03").exists()
assert (root / "Temp" / "Prop03" / "PartA.blend").exists()
assert (root / "Temp" / "Prop03" / "PartB.blend").exists()
make_path("Prop03/PartC.blend", under=root)
# Asset.blend is already in Temp, copy fail
with pytest.raises(FileExistsError):
Opath(root / "Prop03").copy_to(root / "Temp")
# copy folder Prop03 to Temp
Opath(root / "Prop03").copy_to(root / "Temp", overwrite=True)
assert (root / "Temp" / "Prop03" / "PartC.blend").exists()
Reveal in Explorer
from omoospace import Omoospace, reveal_directory
omoos = Omoospace("path/to/omoospace")
reveal_directory(omoos.root_dir)
Oset
omoospace = Omoospace(mini_omoos_path)
makers = Oset[Maker](
[Maker(omoospace, "Alice"), Maker(omoospace, "Bob")], key="name"
)
assert "Alice" in makers
assert "Charlie" not in makers
assert Maker(omoospace, "Bob") in makers
# 测试添加重复属性值的对象(不会被添加)
makers.add(Maker(omoospace, "Alice"))
assert len(makers) == 2
assert "Alice" in makers
assert "Bob" in makers
# 2. 测试Work类型的Oset(验证通用性)
works = Oset[Work](
[Work(omoospace, "Coding"), Work(omoospace, "Testing")], key="name"
)
assert "Coding" in works
assert Work(omoospace, "Testing") in works
assert "Design" not in works
Utils
Normalize a Name