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

0.1.1 #8

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Important note: For sparse point cloud to import you should change in `Structure

Additional option: by searching [F3] for `Meshroom update cameras`, you can copy settings from active camera to all meshroom cameras.

## Changelog:
- 0.1.0 - undisorted is now active, info when no .ply format, update point cloud vis, views named by filenames.
- 0.0.1 - initial working functionality

## TODO:
- seach through node tree and give option which elements import;
- if node is not computed then put some info about it;
Expand Down
134 changes: 62 additions & 72 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,18 @@
"name": "Meshroom importer",
"description": "Imports from .mg file cameras, images, sparse and obj",
"author": "Dawid Huczyński",
"version": (0, 0, 1),
"version": (0, 1, 0),
"blender": (2, 80, 0),
"location": "File > Import > Import Meshroom",
"warning": "This addon is still in development.",
"wiki_url": "",
"category": "Import-Export"}


filepath = r'D:\Koszyk\koszyk.mg'

# module import https://github.com/uhlik/bpy/blob/master/view3d_point_cloud_visualizer.py
# thanks to Jakub Uhlik
vis_mod = 'view3d_point_cloud_visualizer'
if vis_mod in sys.modules.keys() and sys.modules[vis_mod].bl_info['version'] <= (0, 7, 0):
if vis_mod in sys.modules.keys() and sys.modules[vis_mod].bl_info['version'] <= (0, 8, 12):
local_visualizer = False
else:
from . import view3d_point_cloud_visualizer as point_cloud
Expand All @@ -63,11 +61,11 @@ def find_view_layer(coll, lay_coll=None):
return None


def read_meshlab(filepath):
'Handle meshlab file'

def get_meshroom_paths(filepath):
'Handle meshroom file'
cache = os.path.join(os.path.dirname(filepath), 'MeshroomCache')
data = json.load(open(filepath, 'r'))
data
try:
nodeSFM = data['graph']['StructureFromMotion_1']
nodeType = nodeSFM['nodeType']
Expand All @@ -76,9 +74,19 @@ def read_meshlab(filepath):
# cache=cache, nodeType=nodeType, uid0=uid0)
cameras_sfm = nodeSFM['outputs']['outputViewsAndPoses'].format(
cache=cache, nodeType=nodeType, uid0=uid0)
cloud = os.path.join(cache, nodeType, uid0, 'cloud_and_poses.ply')
# sparse = nodeSFM['outputs']['output'].format(
# cache=cache, nodeType=nodeType, uid0=uid0)
sparse = nodeSFM['outputs']['extraInfoFolder'].format(
cache=cache, nodeType=nodeType, uid0=uid0) + 'cloud_and_poses.ply'
except KeyError:
cameras_sfm = cloud = None
cameras_sfm = sparse = None
try:
prepDense = data['graph']['PrepareDenseScene_1']
nodeType = prepDense['nodeType']
uid0 = prepDense['uids']['0']
exr_folder = prepDense['outputs']['output'].format(cache=cache, nodeType=nodeType, uid0=uid0)
except KeyError:
exr_folder = None
try:
nodeMesh = data['graph']['Meshing_1']
dense_obj = nodeMesh['outputs']['output'].format(
Expand All @@ -91,22 +99,26 @@ def read_meshlab(filepath):
cache=cache, nodeType=nodeTex['nodeType'], uid0=nodeTex['uids']['0'])
except KeyError:
tex_obj = None
return (cameras_sfm, cloud, dense_obj, tex_obj)
return (cameras_sfm, sparse, dense_obj, tex_obj, exr_folder)


def import_cameras(cameras_sfm, img_depth):
def import_cameras(cameras_sfm, img_depth, undistorted, exr_folder):
'read camera sfm and imports to blender'
data = json.load(open(cameras_sfm, 'r'))
poses = {x['poseId']: x['pose'] for x in data['poses']}
intrinsics = {x['intrinsicId']: x for x in data['intrinsics']}

#TODO dimensions per camera
render = bpy.context.scene.render
render.resolution_x = int(data['views'][0]['width'])
render.resolution_y = int(data['views'][0]['height'])

for view in data['views']:
view_id = view['viewId']
path = view['path']
if undistorted:
path = os.path.join(exr_folder, f'{view_id}.exr')
else:
path = view['path']
width, height = int(view['width']), int(view['height'])
focal_length = float(view['metadata']['Exif:FocalLength'])
pose = poses[view['poseId']]['transform']
Expand All @@ -130,7 +142,12 @@ def import_cameras(cameras_sfm, img_depth):
bg.display_depth = img_depth

# camera object
ob = bpy.data.objects.new(f'View {view_id}', bcam)
if undistorted:
# TODO: if undistorted then apply some resize, or add separate images in right place.
name = f'View {view_id}'
else:
name = 'View {}'.format(os.path.splitext(os.path.basename(path)))
ob = bpy.data.objects.new(name, bcam)
bpy.context.collection.objects.link(ob)
loc = [float(x) for x in pose['center']]
rot = [float(x) for x in pose['rotation']]
Expand All @@ -140,45 +157,6 @@ def import_cameras(cameras_sfm, img_depth):
ob.location = Vector(loc)


def import_sparse_depricated(cloud):
'''Depricated. Use view3d_point_cloud_visualizer instead.'''
# read .ply file
f = open(cloud, 'r')
ply = f.read()
header = ply[:1000].split('end_header\n')[0].split('\n')
header
assert header[0] == 'ply'
assert header[1].startswith('format ascii')
elements = []
tmp_prop = []
for x in header[2:]:
a = x.split(' ')
if a[0] == 'element':
if tmp_prop:
elements[-1]['props'] = list(tmp_prop)
tmp_prop = []
el = {'name': a[1], 'nr': a[2]}
elements.append(el)
elif a[0] == 'property':
prop = {'name': a[2], 'type': a[1]}
tmp_prop.append(prop)

elements[-1]['props'] = list(tmp_prop)

points = ply.split('end_header\n')[1].split('\n')
if points[-1] == '':
points.pop()

verts = []
for point in points:
verts.append((float(x) for x in point.split()[:3]))

mesh = bpy.data.meshes.new('sparse cloud SFM')
mesh.from_pydata(verts, [], [])
obj = bpy.data.objects.new('sparse cloud SFM', mesh)
bpy.context.collection.objects.link(obj)


def import_object(filepath):
bpy.ops.import_scene.obj(filepath=filepath)
bpy.context.selected_objects[0].matrix_world = Matrix()
Expand All @@ -197,21 +175,21 @@ class import_meshroom(bpy.types.Operator):
directory: bpy.props.StringProperty(
maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})

cameras: bpy.props.BoolProperty(default=True, name='Views', description='Import views as cameras and images')
import_views: bpy.props.BoolProperty(default=True, name='Views', description='Import views as cameras and images')

undistorted: bpy.props.BoolProperty(default=True, name='Undistorted', description='Better, but heavy images')
undistorted: bpy.props.BoolProperty(default=False, name='Undistorted', description='Better, but heavy images')

DEPTH = [
('FRONT', 'FRONT', 'Preview semi transparent image in front of the objects', '', 0),
('BACK', 'BACK', 'Preview image behing objects', '', 1)
]
img_front: bpy.props.EnumProperty(items=DEPTH, name='Depth', description='', default='FRONT')
img_front: bpy.props.EnumProperty(items=DEPTH, name='Image View Depth', description='', default='FRONT')

sparse: bpy.props.BoolProperty(default=True, name='Import SFM', description='')
import_sparse: bpy.props.BoolProperty(default=True, name='Import StructureFromMotion', description='')

dense: bpy.props.BoolProperty(default=False, name='Import dense mesh', description='')
import_dense: bpy.props.BoolProperty(default=False, name='Import Meshing Node', description='')

textured: bpy.props.BoolProperty(default=True, name='Import textured mesh', description='')
import_textured: bpy.props.BoolProperty(default=True, name='Import Texturing Node', description='')

def invoke(self, context, event):
context.window_manager.fileselect_add(self)
Expand All @@ -233,22 +211,34 @@ def execute(self, context):
lay_col = find_view_layer(camera_col)
context.view_layer.active_layer_collection = lay_col
# filepath = PATH
cameras_sfm, cloud, dense_obj, tex_obj = read_meshlab(filepath)
if self.cameras:
import_cameras(cameras_sfm, self.img_front)
cameras_sfm, sparse, dense_obj, tex_obj, exr_folder = get_meshroom_paths(filepath)
if self.import_views:
import_cameras(cameras_sfm, self.img_front, self.undistorted, exr_folder)
lay_col = find_view_layer(col)
context.view_layer.active_layer_collection = lay_col
if self.sparse:
empty = bpy.data.objects.new('sparse cloud SFM', None)
col.objects.link(empty)
empty.select_set(True)
context.view_layer.objects.active = empty
bpy.ops.point_cloud_visualizer.load_ply_to_cache(filepath=cloud)
bpy.ops.point_cloud_visualizer.draw()
if self.dense and dense_obj:
import_object(dense_obj)
if self.textured and tex_obj:
import_object(tex_obj)
if self.import_sparse:
if os.path.exists(sparse):
empty = bpy.data.objects.new('sparse cloud SFM', None)
col.objects.link(empty)
empty.select_set(True)
context.view_layer.objects.active = empty
bpy.ops.point_cloud_visualizer.load_ply_to_cache(filepath=sparse)
bpy.ops.point_cloud_visualizer.draw()
elif os.path.exists(sparse.replace('.ply', '.abc')):
self.report({'ERROR_INVALID_INPUT'}, "You need to use .ply format instead of .abc to use colored pointcloud. "\
"You can always import .abc through Blender alembic importer.")
else:
self.report({'ERROR_INVALID_INPUT'}, "Missing Meshroom reconstruction: StructureFromMotion (.ply format).")
if self.import_dense:
if dense_obj:
import_object(dense_obj)
else:
self.report({'ERROR_INVALID_INPUT'}, "Missing Meshroom reconstruction: Meshing.")
if self.import_textured and tex_obj:
if tex_obj:
import_object(tex_obj)
else:
self.report({'ERROR_INVALID_INPUT'}, "Missing Meshroom reconstruction: Texturing.")
return {"FINISHED"}


Expand Down
Loading