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

Script and tutorial for generating procedural datasets with Blender #1412

Merged
merged 30 commits into from
Oct 21, 2022
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8fb5f17
Add script for creation of procedural datasets with Blender
AndrejOrsula Mar 25, 2022
800eda6
Add tutorial for creation of procedural datasets with Blender
AndrejOrsula Mar 25, 2022
a54be30
Add Future Work section for procedural datasets with Blender
AndrejOrsula Apr 7, 2022
61b6689
Use absolute links in tutorial for prodedural datasets with Blender
AndrejOrsula Apr 7, 2022
e54a450
Improve phrasing in tutorial for procedural datasets with Blender
AndrejOrsula Apr 7, 2022
adec670
Fix error in case an object has no modifiers
AndrejOrsula Apr 7, 2022
cc210d1
Indicate in main() how to export a single model
AndrejOrsula Apr 7, 2022
f9f2133
Place all default parameters at the beginning of script
AndrejOrsula May 23, 2022
c2b6de2
Enable ignore of some objects during export of visual/collision mesh
AndrejOrsula May 23, 2022
08800a5
Add support for simple generation of thumbnails
AndrejOrsula May 23, 2022
9a2ca45
Add Wavefront (.obj) exporter
AndrejOrsula May 23, 2022
3dbc7c2
Add option to specify kwargs
AndrejOrsula May 23, 2022
b565c50
Update script in the example blend file
AndrejOrsula May 23, 2022
a2574f6
Remove finished items from Future Work
AndrejOrsula May 23, 2022
8930433
Fix forward axis for Wavefront (.obj) exporter
AndrejOrsula May 24, 2022
630ae54
Bump LAST_TESTED_VERSION to Blender 3.1.2
AndrejOrsula May 25, 2022
c0c5b81
Set default export directory relative to .blend file
AndrejOrsula May 25, 2022
b8c9498
Support headless generation of datasets
AndrejOrsula May 25, 2022
7abb656
Merge branch 'ign-gazebo6' into blender_procedural_datasets
mabelzhang Jul 14, 2022
e92554e
Apply suggestions from code review
AndrejOrsula Aug 27, 2022
2ab07d4
Add parsing of CLI args and simplify usage
AndrejOrsula Aug 27, 2022
2780dd2
Print cmd to set GZ_SIM_RESOURCE_PATH after the export is finished
AndrejOrsula Aug 27, 2022
d7ebd3a
Default to Wavefront (.obj) file format for visual geometry
AndrejOrsula Aug 28, 2022
dd9480c
Update rock.blend project
AndrejOrsula Aug 29, 2022
5076d9c
Add garden.blend project
AndrejOrsula Aug 29, 2022
bd6b89d
Make script executable
AndrejOrsula Aug 29, 2022
f33a54b
Update tutorial
AndrejOrsula Aug 29, 2022
1ca8e55
Rename `garden.blend` to `woodland.blend`
AndrejOrsula Aug 30, 2022
6c81587
Add attribution for assets used in `woodland.blend`
AndrejOrsula Aug 30, 2022
26686cf
Merge remote-tracking branch 'origin/ign-gazebo6' into blender_proced…
mjcarroll Oct 21, 2022
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
Prev Previous commit
Next Next commit
Enable ignore of some objects during export of visual/collision mesh
Signed-off-by: Andrej Orsula <orsula.andrej@gmail.com>
  • Loading branch information
AndrejOrsula committed May 23, 2022
commit c2b6de26b4aecfaa9f7e4966b4228440bbb2300c
172 changes: 146 additions & 26 deletions examples/scripts/blender/procedural_dataset_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
SUBDIVISION_LEVEL_VISUAL: int = 4
SUBDIVISION_LEVEL_COLLISION: int = 2

IGNORE_OBJECT_NAMES_VISUAL: List[str] = []
IGNORE_OBJECT_NAMES_COLLISION: List[str] = []

SHADE_SMOOTH: bool = True
TEXTURE_SOURCE: str = "none"
TEXTURE_SOURCE_VALUE: Optional[Any] = None
Expand Down Expand Up @@ -86,6 +89,10 @@ class sdf_model_exporter(ModuleType):
# Default args for model being static
DEFAULT_STATIC: bool = STATIC

# Default args for which objects to ignore from mesh exporting
DEFAULT_IGNORE_OBJECT_NAMES_VISUAL: List[str] = IGNORE_OBJECT_NAMES_VISUAL
DEFAULT_IGNORE_OBJECT_NAMES_COLLISION: List[str] = IGNORE_OBJECT_NAMES_COLLISION

# Default args for subdivision level
DEFAULT_SUBDIVISION_LEVEL_VISUAL: int = SUBDIVISION_LEVEL_VISUAL
DEFAULT_SUBDIVISION_LEVEL_COLLISION: int = SUBDIVISION_LEVEL_COLLISION
Expand Down Expand Up @@ -284,6 +291,10 @@ def export(
subdivision_level_visual: int = DEFAULT_SUBDIVISION_LEVEL_VISUAL,
subdivision_level_collision: int = DEFAULT_SUBDIVISION_LEVEL_COLLISION,
shade_smooth: bool = DEFAULT_SHADE_SMOOTH,
ignore_object_names_visual: List[str] = DEFAULT_IGNORE_OBJECT_NAMES_VISUAL,
ignore_object_names_collision: List[
str
] = DEFAULT_IGNORE_OBJECT_NAMES_COLLISION,
generate_thumbnails: bool = False,
model_name_prefix: str = "",
model_name_suffix: str = "",
Expand Down Expand Up @@ -389,6 +400,8 @@ def export(
subdivision_level_visual=subdivision_level_visual,
subdivision_level_collision=subdivision_level_collision,
shade_smooth=shade_smooth,
ignore_object_names_visual=ignore_object_names_visual,
ignore_object_names_collision=ignore_object_names_collision,
)
sdf_data.update(exported_meshes)

Expand Down Expand Up @@ -439,6 +452,22 @@ def export(
for obj in selected_objects_original:
obj.select_set(True)

def _print_bpy(msg: Any, file: Optional[TextIO] = sys.stdout, *args, **kwargs):
"""
Helper print function that also provides output inside the Blender console,
in addition to the system console.
"""

print(msg, file=file, *args, **kwargs)
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == "CONSOLE":
bpy.ops.console.scrollback_append(
{"window": window, "screen": window.screen, "area": area},
text=str(msg),
type="ERROR" if file == sys.stderr else "OUTPUT",
)

@classmethod
def _process_meshes(
cls,
Expand All @@ -450,6 +479,8 @@ def _process_meshes(
subdivision_level_visual: int,
subdivision_level_collision: int,
shade_smooth: bool,
ignore_object_names_visual: List[str],
ignore_object_names_collision: List[str],
) -> Dict[str, str]:
"""
Process and export meshes of the model.
Expand All @@ -461,9 +492,21 @@ def _process_meshes(
if isinstance(filetype_collision, str):
filetype_collision = cls.ExportFormat.from_str(filetype_collision)

# Keep only object names that need processing in the ignore list
meshes_to_process_names = [mesh.name for mesh in meshes_to_process]
ignore_object_names_visual = [
name
for name in ignore_object_names_visual
if name in meshes_to_process_names
]
ignore_object_names_collision = [
name
for name in ignore_object_names_collision
if name in meshes_to_process_names
]

# Deselect all objects
bpy.ops.object.select_all(action="DESELECT")

# Select all desired meshes at the same time
for obj in meshes_to_process:
obj.select_set(True)
Expand All @@ -476,6 +519,8 @@ def _process_meshes(
subdivision_level_visual=subdivision_level_visual,
subdivision_level_collision=subdivision_level_collision,
shade_smooth=shade_smooth,
ignore_object_names_visual=ignore_object_names_visual,
ignore_object_names_collision=ignore_object_names_collision,
)

@classmethod
Expand All @@ -488,6 +533,8 @@ def _export_geometry(
subdivision_level_visual: int,
subdivision_level_collision: int,
shade_smooth: bool,
ignore_object_names_visual: List[str],
ignore_object_names_collision: List[str],
) -> Dict[str, str]:
"""
Export both visual and collision mesh geometry.
Expand All @@ -498,13 +545,15 @@ def _export_geometry(
model_name=model_name,
filetype=filetype_collision,
subdivision_level=subdivision_level_collision,
ignore_object_names=ignore_object_names_collision,
)
filepath_visual = cls._export_geometry_visual(
export_path=export_path,
model_name=model_name,
filetype=filetype_visual,
subdivision_level=subdivision_level_visual,
shade_smooth=shade_smooth,
ignore_object_names=ignore_object_names_visual,
)

return {
Expand All @@ -521,21 +570,30 @@ def _export_geometry_collision(
cls,
export_path: str,
model_name: str,
subdivision_level: int,
filetype: ExportFormat,
subdivision_level: int,
ignore_object_names: List[str],
) -> str:
"""
Export collision geometry of the model with the specified `filetype`.
Method `_pre_export_geometry_collision()` is called before the export.
Method `_post_export_geometry_collision()` is called after the export.
"""

# Hook call before export of collision geometry
cls._pre_export_geometry_collision(subdivision_level=subdivision_level)
cls._pre_export_geometry_collision(
subdivision_level=subdivision_level, ignore_object_names=ignore_object_names
)

return filetype.export(
resulting_export_path = filetype.export(
path.join(export_path, cls.DIRNAME_MESHES_COLLISION, model_name)
)

# Hook call after export of collision geometry
cls._post_export_geometry_collision(ignore_object_names=ignore_object_names)

return resulting_export_path

@classmethod
def _export_geometry_visual(
cls,
Expand All @@ -544,21 +602,30 @@ def _export_geometry_visual(
filetype: ExportFormat,
subdivision_level: int,
shade_smooth: bool,
ignore_object_names: List[str],
) -> str:
"""
Export visual geometry of the model with the specified `filetype`.
Method `_pre_export_geometry_visual()` is called before the export.
Method `_post_export_geometry_visual()` is called after the export.
"""

# Hook call before export of visual geometry
cls._pre_export_geometry_visual(
subdivision_level=subdivision_level, shade_smooth=shade_smooth
subdivision_level=subdivision_level,
shade_smooth=shade_smooth,
ignore_object_names=ignore_object_names,
)

return filetype.export(
resulting_export_path = filetype.export(
path.join(export_path, cls.DIRNAME_MESHES_VISUAL, model_name)
)

# Hook call after export of visual geometry
cls._post_export_geometry_collision(ignore_object_names=ignore_object_names)

return resulting_export_path

@classmethod
def _estimate_inertial_properties(
cls,
Expand Down Expand Up @@ -902,36 +969,69 @@ def _generate_thumbnails(export_path: str):
raise NotImplementedError("Generation of thumbnails is not yet implemented!")

@classmethod
def _pre_export_geometry_collision(cls, subdivision_level: int):
def _pre_export_geometry_collision(
cls, subdivision_level: int, ignore_object_names: List[str] = []
):
"""
A hook that is called before exporting collision geometry. Defaults to no-op.
A hook that is called before exporting collision geometry. Always chain up the
parent implementation.
By default, this hook handles reselecting objects from `ignore_object_names`.
"""

pass # abstractclassmethod
cls.__select_objects(
names=ignore_object_names, type_filter="MESH", select=False
)

@classmethod
def _pre_export_geometry_visual(cls, subdivision_level: int, shade_smooth: bool):
def _post_export_geometry_collision(cls, ignore_object_names: List[str] = []):
"""
A hook that is called before exporting visual geometry. Defaults to no-op.
A hook that is called after exporting collision geometry. Always chain up the
parent implementation.
By default, this hook handles deselecting objects from `ignore_object_names`.
"""

pass # abstractclassmethod
cls.__select_objects(names=ignore_object_names, type_filter="MESH", select=True)

def _print_bpy(msg: Any, file: Optional[TextIO] = sys.stdout, *args, **kwargs):
@classmethod
def _pre_export_geometry_visual(
cls,
subdivision_level: int,
shade_smooth: bool,
ignore_object_names: List[str] = [],
):
"""
Helper print function that also provides output inside the Blender console,
in addition to the system console.
A hook that is called before exporting visual geometry. Always chain up the
parent implementation.
By default, this hook handles reselecting objects from `ignore_object_names`.
"""

print(msg, file=file, *args, **kwargs)
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == "CONSOLE":
bpy.ops.console.scrollback_append(
{"window": window, "screen": window.screen, "area": area},
text=str(msg),
type="ERROR" if file == sys.stderr else "OUTPUT",
)
cls.__select_objects(
names=ignore_object_names, type_filter="MESH", select=False
)

@classmethod
def _post_export_geometry_visual(cls, ignore_object_names: List[str] = []):
"""
A hook that is called after exporting visual geometry. Always chain up the
parent implementation.
By default, this hook handles deselecting objects from `ignore_object_names`.
"""

cls.__select_objects(names=ignore_object_names, type_filter="MESH", select=True)

def __select_objects(
names=List[str], type_filter: Optional[str] = None, select: bool = True
):
"""
(De)select list of objects based on their `names`. List can be filtered
according to the object type via `type_filter`.
"""

for obj in bpy.context.selectable_objects:
if type_filter and obj.type != type_filter:
continue
if obj.name in names:
obj.select_set(select)

@classmethod
def __preprocess_texture_path(
Expand Down Expand Up @@ -1187,12 +1287,20 @@ def generate(
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)

@classmethod
def _pre_export_geometry_collision(cls, subdivision_level: int):
def _pre_export_geometry_collision(
cls, subdivision_level: int, ignore_object_names: List[str] = []
):
"""
A hook that is called before exporting collision geometry. This implementation
adjusts input attributes of the Geometry Nodes system for each mesh.
"""

# Call parent impl
sdf_model_exporter._pre_export_geometry_collision(
subdivision_level=subdivision_level,
ignore_object_names=ignore_object_names,
)

global current_seed
selected_meshes = bpy.context.selected_objects
for obj in selected_meshes:
Expand All @@ -1211,12 +1319,24 @@ def _pre_export_geometry_collision(cls, subdivision_level: int):
cls.__trigger_modifier_update(obj)

@classmethod
def _pre_export_geometry_visual(cls, subdivision_level: int, shade_smooth: bool):
def _pre_export_geometry_visual(
cls,
subdivision_level: int,
shade_smooth: bool,
ignore_object_names: List[str] = [],
):
"""
A hook that is called before exporting visual geometry. This implementation
adjusts input attributes of the Geometry Nodes system for each mesh.
"""

# Call parent impl
sdf_model_exporter._pre_export_geometry_visual(
subdivision_level=subdivision_level,
shade_smooth=shade_smooth,
ignore_object_names=ignore_object_names,
)

global current_seed
selected_meshes = bpy.context.selected_objects
for obj in selected_meshes:
Expand Down