feat: add Cesium semantic inspection mode
This commit is contained in:
@@ -29,6 +29,16 @@ from osmassets.materials import CESIUM_EXPORT_PROPERTY, link_alpha_clip # noqa:
|
||||
# mesh's shared slots can recognise its own output and leave it alone.
|
||||
EXPORT_PREFIX = "Cesium "
|
||||
|
||||
# Top-level collections are the authoring scene's stable semantic boundary.
|
||||
# Their numeric prefixes are draw-order labels in Blender, not part of the
|
||||
# Cesium asset contract below.
|
||||
SEMANTIC_ASSETS = (
|
||||
("roads", "道路", ("03_Roads",)),
|
||||
("buildings", "建筑", ("04_Buildings",)),
|
||||
("vegetation", "绿化与设施", ("02_Green", "05_Props")),
|
||||
("water", "水体", ("01_Water",)),
|
||||
)
|
||||
|
||||
# Fraction of its own albedo a cut-out foliage material emits, to keep the
|
||||
# shadowed side of a crown off Cesium's near-black ambient floor. Kept well
|
||||
# under the 0.18 the buildings use: a tree still has to read as lit from one
|
||||
@@ -544,6 +554,54 @@ def triangulate_mesh(obj):
|
||||
obj.modifiers.remove(modifier)
|
||||
|
||||
|
||||
def export_glb(filepath, meshes):
|
||||
"""Export a prepared object subset without changing authoring visibility."""
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in meshes:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = meshes[0] if meshes else None
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=filepath,
|
||||
export_format="GLB",
|
||||
use_selection=True,
|
||||
export_apply=False,
|
||||
export_texcoords=True,
|
||||
export_normals=True,
|
||||
export_materials="EXPORT",
|
||||
export_image_format="AUTO",
|
||||
export_tangents=True,
|
||||
export_extras=True,
|
||||
export_cameras=False,
|
||||
export_lights=False,
|
||||
)
|
||||
|
||||
|
||||
def semantic_asset_specs(glb_path, meshes):
|
||||
"""Derive optional inspection assets from the scene's named collections."""
|
||||
stem, extension = os.path.splitext(glb_path)
|
||||
by_collection = {}
|
||||
for collection in bpy.context.scene.collection.children:
|
||||
by_collection[collection.name] = set(collection.all_objects)
|
||||
|
||||
specs = []
|
||||
for asset_id, label, collection_names in SEMANTIC_ASSETS:
|
||||
objects = set()
|
||||
for name in collection_names:
|
||||
objects.update(by_collection.get(name, set()))
|
||||
subset = [obj for obj in meshes if obj in objects]
|
||||
if not subset:
|
||||
continue
|
||||
path = stem + "-" + asset_id + extension
|
||||
specs.append({
|
||||
"id": asset_id,
|
||||
"label": label,
|
||||
"path": path,
|
||||
"meshes": subset,
|
||||
})
|
||||
return specs
|
||||
|
||||
|
||||
def export(args):
|
||||
if not os.path.exists(args["blend"]):
|
||||
raise FileNotFoundError(args["blend"])
|
||||
@@ -583,30 +641,10 @@ def export(args):
|
||||
material_map[source.name] = make_export_material(source)
|
||||
slot.material = material_map[source.name]
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in meshes:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = meshes[0] if meshes else None
|
||||
|
||||
os.makedirs(os.path.dirname(args["glb"]), exist_ok=True)
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=args["glb"],
|
||||
export_format="GLB",
|
||||
use_selection=True,
|
||||
export_apply=False,
|
||||
export_texcoords=True,
|
||||
export_normals=True,
|
||||
export_materials="EXPORT",
|
||||
export_image_format="AUTO",
|
||||
# The foliage materials carry a normal map, and glTF leaves tangent
|
||||
# derivation to the renderer when TANGENT is absent. On thin
|
||||
# double-sided leaf cards that derivation is unreliable, so ship real
|
||||
# tangents — it costs four floats a vertex on meshes this small.
|
||||
export_tangents=True,
|
||||
export_extras=True,
|
||||
export_cameras=False,
|
||||
export_lights=False,
|
||||
)
|
||||
export_glb(args["glb"], meshes)
|
||||
semantic_assets = semantic_asset_specs(args["glb"], meshes)
|
||||
for asset in semantic_assets:
|
||||
export_glb(asset["path"], asset["meshes"])
|
||||
|
||||
scene = bpy.context.scene
|
||||
try:
|
||||
@@ -626,7 +664,14 @@ def export(args):
|
||||
"type": "model",
|
||||
"url": os.path.basename(args["glb"]),
|
||||
"enabled": True,
|
||||
}],
|
||||
}] + [{
|
||||
"id": asset["id"],
|
||||
"label": asset["label"],
|
||||
"type": "model",
|
||||
"url": os.path.basename(asset["path"]),
|
||||
"enabled": False,
|
||||
"category": "semantic",
|
||||
} for asset in semantic_assets],
|
||||
"coordinate_system": "local ENU meters (X east, Y north, Z up)",
|
||||
"heading_correction_degrees": -90.0,
|
||||
"anchor": {"longitude": center_lon, "latitude": center_lat, "height": 0.35},
|
||||
@@ -660,6 +705,7 @@ def export(args):
|
||||
print("CESIUM_EXPORT_DONE", json.dumps({
|
||||
"glb": args["glb"], "metadata": args["metadata"],
|
||||
"meshes": len(meshes), "materials": len(material_map),
|
||||
"semantic_assets": [asset["id"] for asset in semantic_assets],
|
||||
"anchor": [center_lon, center_lat],
|
||||
}, ensure_ascii=True))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user