From 847454ee333df9b64dbf86d1eab63d2f3ceb49e4 Mon Sep 17 00:00:00 2001 From: Jakub Uhlik Date: Sun, 1 Mar 2020 19:54:48 +0100 Subject: [PATCH] cleanup --- space_view3d_point_cloud_visualizer.py | 5463 +++++++----------------- 1 file changed, 1617 insertions(+), 3846 deletions(-) diff --git a/space_view3d_point_cloud_visualizer.py b/space_view3d_point_cloud_visualizer.py index c06e0d9..2778587 100755 --- a/space_view3d_point_cloud_visualizer.py +++ b/space_view3d_point_cloud_visualizer.py @@ -5360,789 +5360,86 @@ def sample(): self.cs = cs[:] -class PCVIVSampler(): - def __init__(self, context, o, target, rnd, percentage=1.0, triangulate=True, use_modifiers=True, source=None, colorize=None, constant_color=None, vcols=None, uvtex=None, vgroup=None, ): - log("{}:".format(self.__class__.__name__), 0) - - def remap(v, min1, max1, min2, max2, ): - def clamp(v, vmin, vmax): - if(vmax <= vmin): - raise ValueError("Maximum value is smaller than or equal to minimum.") - if(v <= vmin): - return vmin - if(v >= vmax): - return vmax - return v - - def normalize(v, vmin, vmax): - return (v - vmin) / (vmax - vmin) - - def interpolate(nv, vmin, vmax): - return vmin + (vmax - vmin) * nv - - if(max1 - min1 == 0): - # handle zero division when min1 = max1 - return min2 - - r = interpolate(normalize(v, min1, max1), min2, max2) - return r - - owner = None - if(use_modifiers and target.modifiers): - depsgraph = context.evaluated_depsgraph_get() - owner = target.evaluated_get(depsgraph) - me = owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph, ) - else: - owner = target - me = owner.to_mesh(preserve_all_data_layers=True, depsgraph=None, ) - - bm = bmesh.new() - bm.from_mesh(me) - if(not triangulate): - if(colorize in ('VCOLS', 'UVTEX', 'GROUP_MONO', 'GROUP_COLOR', )): - bmesh.ops.triangulate(bm, faces=bm.faces) - else: - bmesh.ops.triangulate(bm, faces=bm.faces) - bm.verts.ensure_lookup_table() - bm.faces.ensure_lookup_table() - - if(source is None): - source = 'VERTICES' - - if(len(bm.verts) == 0): - raise Exception("Mesh has no vertices") - if(colorize in ('UVTEX', 'VCOLS', 'VIEWPORT_DISPLAY_COLOR', )): - if(len(bm.faces) == 0): - raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - try: - if(len(target.data.materials) == 0): - raise Exception("Cannot find any material") - materials = target.data.materials - except Exception as e: - raise Exception(str(e)) - if(colorize == 'UVTEX'): - try: - if(target.active_material is None): - raise Exception("Cannot find active material") - uvtexnode = target.active_material.node_tree.nodes.active - if(uvtexnode is None): - raise Exception("Cannot find active image texture in active material") - uvimage = uvtexnode.image - if(uvimage is None): - raise Exception("Cannot find active image texture with loaded image in active material") - uvimage.update() - uvarray = np.asarray(uvimage.pixels) - uvarray = uvarray.reshape((uvimage.size[1], uvimage.size[0], 4)) - uvlayer = bm.loops.layers.uv.active - if(uvlayer is None): - raise Exception("Cannot find active UV layout") - except Exception as e: - raise Exception(str(e)) - if(colorize == 'VCOLS'): - try: - col_layer = bm.loops.layers.color.active - if(col_layer is None): - raise Exception() - except Exception: - raise Exception("Cannot find active vertex colors") - if(colorize in ('GROUP_MONO', 'GROUP_COLOR')): - try: - group_layer = bm.verts.layers.deform.active - if(group_layer is None): - raise Exception() - group_layer_index = target.vertex_groups.active.index - except Exception: - raise Exception("Cannot find active vertex group") - - # if(percentage < 1.0): - # if(source == 'FACES'): - # rnd_layer = bm.faces.layers.float.new('face_random') - # for f in bm.faces: - # f[rnd_layer] = rnd.random() - # if(source == 'VERTICES'): - # rnd_layer = bm.verts.layers.float.new('vertex_random') - # for v in bm.verts: - # v[rnd_layer] = rnd.random() - - vs = [] - ns = [] - cs = [] - - if(source == 'FACES'): - for f in bm.faces: - if(percentage < 1.0): - # if(f[rnd_layer] > percentage): - # continue - if(rnd.random() > percentage): - continue - - v = f.calc_center_median() - vs.append(v.to_tuple()) - ns.append(f.normal.to_tuple()) - - if(colorize is None): - cs.append((1.0, 0.0, 0.0, )) - elif(colorize == 'CONSTANT'): - cs.append(constant_color) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - c = materials[f.material_index].diffuse_color[:3] - c = [v ** (1 / 2.2) for v in c] - cs.append(c) - elif(colorize == 'VCOLS'): - ws = poly_3d_calc([f.verts[0].co, f.verts[1].co, f.verts[2].co, ], v) - ac = f.loops[0][col_layer][:3] - bc = f.loops[1][col_layer][:3] - cc = f.loops[2][col_layer][:3] - r = ac[0] * ws[0] + bc[0] * ws[1] + cc[0] * ws[2] - g = ac[1] * ws[0] + bc[1] * ws[1] + cc[1] * ws[2] - b = ac[2] * ws[0] + bc[2] * ws[1] + cc[2] * ws[2] - cs.append((r, g, b, )) - elif(colorize == 'UVTEX'): - uvtriangle = [] - for l in f.loops: - uvtriangle.append(Vector(l[uvlayer].uv.to_tuple() + (0.0, ))) - uvpoint = barycentric_transform(v, f.verts[0].co, f.verts[1].co, f.verts[2].co, *uvtriangle, ) - w, h = uvimage.size - # x,y % 1.0 to wrap around if uv coordinate is outside 0.0-1.0 range - x = int(round(remap(uvpoint.x % 1.0, 0.0, 1.0, 0, w - 1))) - y = int(round(remap(uvpoint.y % 1.0, 0.0, 1.0, 0, h - 1))) - cs.append(tuple(uvarray[y][x][:3].tolist())) - elif(colorize == 'GROUP_MONO'): - ws = poly_3d_calc([f.verts[0].co, f.verts[1].co, f.verts[2].co, ], v) - aw = f.verts[0][group_layer].get(group_layer_index, 0.0) - bw = f.verts[1][group_layer].get(group_layer_index, 0.0) - cw = f.verts[2][group_layer].get(group_layer_index, 0.0) - m = aw * ws[0] + bw * ws[1] + cw * ws[2] - cs.append((m, m, m, )) - elif(colorize == 'GROUP_COLOR'): - ws = poly_3d_calc([f.verts[0].co, f.verts[1].co, f.verts[2].co, ], v) - aw = f.verts[0][group_layer].get(group_layer_index, 0.0) - bw = f.verts[1][group_layer].get(group_layer_index, 0.0) - cw = f.verts[2][group_layer].get(group_layer_index, 0.0) - m = aw * ws[0] + bw * ws[1] + cw * ws[2] - hue = remap(1.0 - m, 0.0, 1.0, 0.0, 1 / 1.5) - c = Color() - c.hsv = (hue, 1.0, 1.0, ) - cs.append((c.r, c.g, c.b, )) - else: - # source == 'VERTICES' - for v in bm.verts: - if(percentage < 1.0): - # if(v[rnd_layer] > percentage): - # continue - if(rnd.random() > percentage): - continue - - if(len(v.link_loops) == 0 and colorize in ('UVTEX', 'VCOLS', 'VIEWPORT_DISPLAY_COLOR', )): - # single vertex without faces, skip when faces are required for colorizing - continue - - vs.append(v.co.to_tuple()) - ns.append(v.normal.to_tuple()) - - if(colorize is None): - cs.append((1.0, 0.0, 0.0, )) - elif(colorize == 'CONSTANT'): - cs.append(constant_color) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - r = 0.0 - g = 0.0 - b = 0.0 - lfs = v.link_faces - for f in lfs: - c = materials[f.material_index].diffuse_color[:3] - c = [v ** (1 / 2.2) for v in c] - r += c[0] - g += c[1] - b += c[2] - r /= len(lfs) - g /= len(lfs) - b /= len(lfs) - cs.append((r, g, b, )) - elif(colorize == 'VCOLS'): - ls = v.link_loops - r = 0.0 - g = 0.0 - b = 0.0 - for l in ls: - c = l[col_layer][:3] - r += c[0] - g += c[1] - b += c[2] - r /= len(ls) - g /= len(ls) - b /= len(ls) - cs.append((r, g, b, )) - elif(colorize == 'UVTEX'): - ls = v.link_loops - w, h = uvimage.size - r = 0.0 - g = 0.0 - b = 0.0 - for l in ls: - uvloc = l[uvlayer].uv.to_tuple() - # x,y % 1.0 to wrap around if uv coordinate is outside 0.0-1.0 range - x = int(round(remap(uvloc[0] % 1.0, 0.0, 1.0, 0, w - 1))) - y = int(round(remap(uvloc[1] % 1.0, 0.0, 1.0, 0, h - 1))) - c = tuple(uvarray[y][x][:3].tolist()) - r += c[0] - g += c[1] - b += c[2] - r /= len(ls) - g /= len(ls) - b /= len(ls) - cs.append((r, g, b, )) - elif(colorize == 'GROUP_MONO'): - w = v[group_layer].get(group_layer_index, 0.0) - cs.append((w, w, w, )) - elif(colorize == 'GROUP_COLOR'): - w = v[group_layer].get(group_layer_index, 0.0) - hue = remap(1.0 - w, 0.0, 1.0, 0.0, 1 / 1.5) - c = Color() - c.hsv = (hue, 1.0, 1.0, ) - cs.append((c.r, c.g, c.b, )) - - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] - - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] - - bm.free() - owner.to_mesh_clear() +class PCV_OT_init(Operator): + bl_idname = "point_cloud_visualizer.init" + bl_label = "init" + + def execute(self, context): + PCVManager.init() + context.area.tag_redraw() + return {'FINISHED'} -class PCVIVDraftSampler(): - def __init__(self, context, target, percentage=1.0, seed=0, colorize=None, constant_color=None, ): - log("{}:".format(self.__class__.__name__), 0) - - if(colorize is None): - colorize = 'CONSTANT' - if(constant_color is None): - constant_color = (1.0, 0.0, 0.0, ) - - me = target.data - - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize in ('VIEWPORT_DISPLAY_COLOR', )): - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - if(len(target.data.materials) == 0): - raise Exception("Cannot find any material") - materials = target.data.materials - - vs = [] - ns = [] - cs = [] - - np.random.seed(seed=seed) - rnd = np.random.uniform(low=0.0, high=1.0, size=len(me.polygons), ) - - for i, f in enumerate(me.polygons): - if(percentage < 1.0): - if(rnd[i] > percentage): - continue - - v = f.center - vs.append(v.to_tuple()) - ns.append(f.normal.to_tuple()) - - if(colorize == 'CONSTANT'): - cs.append(constant_color) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - c = materials[f.material_index].diffuse_color[:3] - c = [v ** (1 / 2.2) for v in c] - cs.append(c) - - # # skip normals.. - # n = len(vs) - # ns = np.column_stack((np.full(n, 0.0, dtype=np.float32, ), - # np.full(n, 0.0, dtype=np.float32, ), - # np.full(n, 1.0, dtype=np.float32, ), )) - - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] - - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] +class PCV_OT_deinit(Operator): + bl_idname = "point_cloud_visualizer.deinit" + bl_label = "deinit" + + def execute(self, context): + PCVManager.deinit() + context.area.tag_redraw() + return {'FINISHED'} -class PCVIVDraftPercentageNumpySampler(): - def __init__(self, context, target, percentage=1.0, seed=0, colorize=None, constant_color=None, ): - log("{}:".format(self.__class__.__name__), 0) - - if(colorize is None): - colorize = 'CONSTANT' - if(constant_color is None): - constant_color = (1.0, 0.0, 0.0, ) +class PCV_OT_gc(Operator): + bl_idname = "point_cloud_visualizer.gc" + bl_label = "gc" + + def execute(self, context): + PCVManager.gc() + return {'FINISHED'} + + +class PCV_OT_draw(Operator): + bl_idname = "point_cloud_visualizer.draw" + bl_label = "Draw" + bl_description = "Draw point cloud to viewport" + + @classmethod + def poll(cls, context): + if(context.object is None): + return False - me = target.data + pcv = context.object.point_cloud_visualizer + ok = False + cached = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + cached = True + if(not v['draw']): + ok = True + if(not ok and pcv.filepath != "" and pcv.uuid != "" and not cached): + ok = True + return ok + + def execute(self, context): + PCVManager.init() - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize in ('VIEWPORT_DISPLAY_COLOR', )): - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - if(len(target.data.materials) == 0): - raise Exception("Cannot find any material") - materials = target.data.materials + pcv = context.object.point_cloud_visualizer - vs = [] - ns = [] - cs = [] + if(pcv.uuid not in PCVManager.cache): + pcv.uuid = "" + ok = PCVManager.load_ply_to_cache(self, context) + if(not ok): + return {'CANCELLED'} - l = len(me.polygons) - - np.random.seed(seed=seed) - rnd = np.random.uniform(low=0.0, high=1.0, size=l, ) - - centers = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('center', centers, ) - centers.shape = (l, 3) - - normals = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('normal', normals, ) - normals.shape = (l, 3) - - rnd[rnd < percentage] = 1 - rnd[rnd < 1] = 0 - indices = [] - for i, v in enumerate(rnd): - if(v): - indices.append(i) - indices = np.array(indices, dtype=np.int, ) - - li = len(indices) - if(colorize == 'CONSTANT'): - colors = np.column_stack((np.full(li, constant_color[0], dtype=np.float32, ), - np.full(li, constant_color[1], dtype=np.float32, ), - np.full(li, constant_color[2], dtype=np.float32, ), )) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - colors = np.zeros((li, 3), dtype=np.float32, ) - for i, index in enumerate(indices): - p = me.polygons[index] - c = materials[p.material_index].diffuse_color[:3] - c = [v ** (1 / 2.2) for v in c] - colors[i][0] = c[0] - colors[i][1] = c[1] - colors[i][2] = c[2] - - vs = np.take(centers, indices, axis=0, ) - ns = np.take(normals, indices, axis=0, ) - cs = colors - - # NOTE: shuffle can be removed if i am not going to use all points, shuffle also slows everything down + c = PCVManager.cache[pcv.uuid] + c['draw'] = True - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] + context.area.tag_redraw() - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] + return {'FINISHED'} -class PCVIVDraftFixedCountNumpySampler(): - def __init__(self, context, target, count=-1, seed=0, colorize=None, constant_color=None, ): - # log("{}:".format(self.__class__.__name__), 0) - # log("target: {}, count: {}".format(target, count, ), 1) - - if(colorize is None): - colorize = 'CONSTANT' - if(constant_color is None): - constant_color = (1.0, 0.0, 0.0, ) - - me = target.data - - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize in ('VIEWPORT_DISPLAY_COLOR', )): - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - if(len(target.data.materials) == 0): - raise Exception("Cannot find any material") - materials = target.data.materials - - vs = [] - ns = [] - cs = [] - - l = len(me.polygons) - if(count == -1): - count = l - if(count > l): - count = l - - np.random.seed(seed=seed) - - centers = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('center', centers, ) - centers.shape = (l, 3) - - normals = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('normal', normals, ) - normals.shape = (l, 3) - - indices = np.random.randint(0, l, count, dtype=np.int, ) - - material_indices = np.zeros(l, dtype=np.int, ) - me.polygons.foreach_get('material_index', material_indices, ) - material_colors = np.zeros((len(materials), 3), dtype=np.float32, ) - for i, m in enumerate(materials): - mc = m.diffuse_color[:3] - material_colors[i][0] = mc[0] ** (1 / 2.2) - material_colors[i][1] = mc[1] ** (1 / 2.2) - material_colors[i][2] = mc[2] ** (1 / 2.2) - - li = len(indices) - if(colorize == 'CONSTANT'): - colors = np.column_stack((np.full(li, constant_color[0], dtype=np.float32, ), - np.full(li, constant_color[1], dtype=np.float32, ), - np.full(li, constant_color[2], dtype=np.float32, ), )) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - colors = np.zeros((li, 3), dtype=np.float32, ) - colors = np.take(material_colors, material_indices, axis=0,) - - if(l == count): - vs = centers - ns = normals - cs = colors - else: - vs = np.take(centers, indices, axis=0, ) - ns = np.take(normals, indices, axis=0, ) - cs = np.take(colors, indices, axis=0, ) - - # NOTE: shuffle can be removed if i am not going to use all points, shuffle also slows everything down, but display won't work as nicely as it does now.. - - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] - - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] - - -class PCVIVDraftWeightedFixedCountNumpySampler(): - def __init__(self, context, target, count=-1, seed=0, colorize=None, constant_color=None, ): - # log("{}:".format(self.__class__.__name__), 0) - # log("target: {}, count: {}".format(target, count, ), 1) - - if(colorize is None): - colorize = 'CONSTANT' - if(constant_color is None): - constant_color = (1.0, 0.0, 0.0, ) - - me = target.data - - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize in ('VIEWPORT_DISPLAY_COLOR', )): - if(len(me.polygons) == 0): - raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - if(len(target.data.materials) == 0): - raise Exception("Cannot find any material") - materials = target.data.materials - - vs = [] - ns = [] - cs = [] - - l = len(me.polygons) - if(count == -1): - count = l - if(count > l): - count = l - - np.random.seed(seed=seed) - - centers = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('center', centers, ) - centers.shape = (l, 3) - - normals = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('normal', normals, ) - normals.shape = (l, 3) - - # indices = np.random.randint(0, l, count, dtype=np.int, ) - - choices = np.indices((l, ), dtype=np.int, ) - choices.shape = (l, ) - weights = np.zeros(l, dtype=np.float32, ) - me.polygons.foreach_get('area', weights, ) - # # normalize - # weights *= (1.0 / np.max(weights)) - # make it all sum to 1.0 - weights *= 1.0 / np.sum(weights) - indices = np.random.choice(choices, size=count, replace=False, p=weights, ) - - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - material_indices = np.zeros(l, dtype=np.int, ) - me.polygons.foreach_get('material_index', material_indices, ) - material_colors = np.zeros((len(materials), 3), dtype=np.float32, ) - for i, m in enumerate(materials): - mc = m.diffuse_color[:3] - material_colors[i][0] = mc[0] ** (1 / 2.2) - material_colors[i][1] = mc[1] ** (1 / 2.2) - material_colors[i][2] = mc[2] ** (1 / 2.2) - - li = len(indices) - if(colorize == 'CONSTANT'): - colors = np.column_stack((np.full(l, constant_color[0] ** (1 / 2.2), dtype=np.float32, ), - np.full(l, constant_color[1] ** (1 / 2.2), dtype=np.float32, ), - np.full(l, constant_color[2] ** (1 / 2.2), dtype=np.float32, ), )) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - colors = np.zeros((li, 3), dtype=np.float32, ) - colors = np.take(material_colors, material_indices, axis=0,) - - if(l == count): - vs = centers - ns = normals - cs = colors - else: - vs = np.take(centers, indices, axis=0, ) - ns = np.take(normals, indices, axis=0, ) - cs = np.take(colors, indices, axis=0, ) - - # NOTE: shuffle can be removed if i am not going to use all points, shuffle also slows everything down, but display won't work as nicely as it does now.. - - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] - - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] - - -class PCVIVDraftWeightedFixedCountNumpyWeightedColorsSampler(): - def __init__(self, context, target, count=-1, seed=0, colorize=None, constant_color=None, use_face_area=None, use_material_factors=None, ): - if(colorize is None): - colorize = 'CONSTANT' - if(constant_color is None): - # constant_color = (1.0, 0.0, 0.0, ) - constant_color = (1.0, 0.0, 1.0, ) - if(use_material_factors is None and colorize == 'VIEWPORT_DISPLAY_COLOR'): - use_material_factors = False - if(use_material_factors): - if(colorize == 'CONSTANT'): - use_material_factors = False - - me = target.data - - if(len(me.polygons) == 0): - # raise Exception("Mesh has no faces") - # no polygons to generate from, use origin - self.vs = np.array(((0.0, 0.0, 0.0, ), ), dtype=np.float32, ) - self.ns = np.array(((0.0, 0.0, 1.0, ), ), dtype=np.float32, ) - self.cs = np.array(((1.0, 0.0, 1.0, ), ), dtype=np.float32, ) - return - # if(colorize in ('VIEWPORT_DISPLAY_COLOR', )): - # if(len(me.polygons) == 0): - # raise Exception("Mesh has no faces") - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - if(len(target.data.materials) == 0): - # raise Exception("Cannot find any material") - # no materials, set to constant - colorize = 'CONSTANT' - constant_color = (1.0, 0.0, 1.0, ) - materials = target.data.materials - if(None in materials[:]): - # if there is empty slot, abort it and set to constant - # TODO: make some workaround empty slots, this would require check for polygons with that empty material assigned and replacing that with constant color - colorize = 'CONSTANT' - constant_color = (1.0, 0.0, 1.0, ) - - vs = [] - ns = [] - cs = [] - - l = len(me.polygons) - if(count == -1): - count = l - if(count > l): - count = l - - np.random.seed(seed=seed, ) - - centers = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('center', centers, ) - centers.shape = (l, 3) - - normals = np.zeros((l * 3), dtype=np.float32, ) - me.polygons.foreach_get('normal', normals, ) - normals.shape = (l, 3) - - choices = np.indices((l, ), dtype=np.int, ) - choices.shape = (l, ) - weights = np.zeros(l, dtype=np.float32, ) - me.polygons.foreach_get('area', weights, ) - # make it all sum to 1.0 - weights *= 1.0 / np.sum(weights) - indices = np.random.choice(choices, size=count, replace=False, p=weights, ) - - if(colorize == 'VIEWPORT_DISPLAY_COLOR'): - material_indices = np.zeros(l, dtype=np.int, ) - me.polygons.foreach_get('material_index', material_indices, ) - material_colors = np.zeros((len(materials), 3), dtype=np.float32, ) - material_factors = np.zeros((len(materials)), dtype=np.float32, ) - for i, m in enumerate(materials): - mc = m.diffuse_color[:3] - material_colors[i][0] = mc[0] ** (1 / 2.2) - material_colors[i][1] = mc[1] ** (1 / 2.2) - material_colors[i][2] = mc[2] ** (1 / 2.2) - material_factors[i] = m.pcv_instance_visualizer.factor - - if(use_material_factors): - material_weights = np.take(material_factors, material_indices, axis=0, ) - # material_weights *= 1.0 / np.sum(material_weights) - material_weights *= 1.0 / np.sum(material_weights) - # weights = material_weights - - if(use_face_area): - weights = (weights + material_weights) / 2.0 - else: - weights = material_weights - - indices = np.random.choice(choices, size=count, replace=False, p=weights, ) - - li = len(indices) - if(colorize == 'CONSTANT'): - colors = np.column_stack((np.full(l, constant_color[0] ** (1 / 2.2), dtype=np.float32, ), - np.full(l, constant_color[1] ** (1 / 2.2), dtype=np.float32, ), - np.full(l, constant_color[2] ** (1 / 2.2), dtype=np.float32, ), )) - elif(colorize == 'VIEWPORT_DISPLAY_COLOR'): - colors = np.zeros((li, 3), dtype=np.float32, ) - colors = np.take(material_colors, material_indices, axis=0, ) - - if(l == count): - vs = centers - ns = normals - cs = colors - else: - vs = np.take(centers, indices, axis=0, ) - ns = np.take(normals, indices, axis=0, ) - cs = np.take(colors, indices, axis=0, ) - - # NOTE: shuffle can be removed if i am not going to use all points, shuffle also slows everything down, but display won't work as nicely as it does now.. - - # and shuffle.. - a = np.concatenate((vs, ns, cs), axis=1, ) - np.random.shuffle(a) - vs = a[:, :3] - ns = a[:, 3:6] - cs = a[:, 6:] - - self.vs = vs[:] - self.ns = ns[:] - self.cs = cs[:] - - -class PCV_OT_init(Operator): - bl_idname = "point_cloud_visualizer.init" - bl_label = "init" - - def execute(self, context): - PCVManager.init() - context.area.tag_redraw() - return {'FINISHED'} - - -class PCV_OT_deinit(Operator): - bl_idname = "point_cloud_visualizer.deinit" - bl_label = "deinit" - - def execute(self, context): - PCVManager.deinit() - context.area.tag_redraw() - return {'FINISHED'} - - -class PCV_OT_gc(Operator): - bl_idname = "point_cloud_visualizer.gc" - bl_label = "gc" - - def execute(self, context): - PCVManager.gc() - return {'FINISHED'} - - -class PCV_OT_draw(Operator): - bl_idname = "point_cloud_visualizer.draw" - bl_label = "Draw" - bl_description = "Draw point cloud to viewport" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - cached = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - cached = True - if(not v['draw']): - ok = True - if(not ok and pcv.filepath != "" and pcv.uuid != "" and not cached): - ok = True - return ok - - def execute(self, context): - PCVManager.init() - - pcv = context.object.point_cloud_visualizer - - if(pcv.uuid not in PCVManager.cache): - pcv.uuid = "" - ok = PCVManager.load_ply_to_cache(self, context) - if(not ok): - return {'CANCELLED'} - - c = PCVManager.cache[pcv.uuid] - c['draw'] = True - - context.area.tag_redraw() - - return {'FINISHED'} - - -class PCV_OT_erase(Operator): - bl_idname = "point_cloud_visualizer.erase" - bl_label = "Erase" - bl_description = "Erase point cloud from viewport" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False +class PCV_OT_erase(Operator): + bl_idname = "point_cloud_visualizer.erase" + bl_label = "Erase" + bl_description = "Erase point cloud from viewport" + + @classmethod + def poll(cls, context): + if(context.object is None): + return False pcv = context.object.point_cloud_visualizer ok = False @@ -7437,921 +6734,27 @@ def gen_color(bm, result, location, normal, index, distance, ): i['y'] = l[1] i['z'] = l[2] - log("postprocessing..", 1) - # split back - vs = np.column_stack((a['x'], a['y'], a['z'], )) - ns = np.column_stack((a['nx'], a['ny'], a['nz'], )) - cs = np.column_stack((a['red'], a['green'], a['blue'], a['alpha'], )) - vs = vs.astype(np.float32) - ns = ns.astype(np.float32) - cs = cs.astype(np.float32) - - # unapply parent matrix to points - m = c['object'].matrix_world.copy() - m = m.inverted() - vs, ns = apply_matrix(m, vs, ns, ) - vs = vs.astype(np.float32) - ns = ns.astype(np.float32) - - # cleanup - if(pcv.filter_project_colorize): - bm.free() - # owner.to_mesh_clear() - - collection.objects.unlink(target) - bpy.data.objects.remove(target) - bpy.data.meshes.remove(target_mesh) - o.to_mesh_clear() - - # put to cache.. - pcv = context.object.point_cloud_visualizer - PCVManager.update(pcv.uuid, vs, ns, cs, ) - - # if(debug_mode()): - # pr.disable() - # s = io.StringIO() - # sortby = 'cumulative' - # ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - # ps.print_stats() - # print(s.getvalue()) - - _d = datetime.timedelta(seconds=time.time() - _t) - log("completed in {}.".format(_d), 1) - - return {'FINISHED'} - - -class PCV_OT_filter_remove_color(Operator): - bl_idname = "point_cloud_visualizer.filter_remove_color" - bl_label = "Select Color" - bl_description = "Select points with exact/similar color" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - ok = True - return ok - - def execute(self, context): - log("Remove Color:", 0) - _t = time.time() - - pcv = context.object.point_cloud_visualizer - # cache item - c = PCVManager.cache[pcv.uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - # join to indexed points - l = len(vs) - dt = [('x', ' 1/2, plus and minus => full range - dh = pcv.filter_remove_color_delta_hue / 2 - # only for hue, because i take in consideration its radial nature - ds = pcv.filter_remove_color_delta_saturation - dv = pcv.filter_remove_color_delta_value - uh = pcv.filter_remove_color_delta_hue_use - us = pcv.filter_remove_color_delta_saturation_use - uv = pcv.filter_remove_color_delta_value_use - - prgr = Progress(len(points), 1) - for p in points: - prgr.step() - # get point color - c = Color((p['red'], p['green'], p['blue'])) - # check for more or less same color, a few decimals should be more than enough, ply should have 8bit colors - fpr = 5 - same = (round(c.r, fpr) == round(rmcolor.r, fpr), - round(c.g, fpr) == round(rmcolor.g, fpr), - round(c.b, fpr) == round(rmcolor.b, fpr)) - if(all(same)): - indexes.append(p['index']) - continue - - # check - h = False - s = False - v = False - if(uh): - rm_hue = rmcolor.h - hd = min(abs(rm_hue - c.h), 1.0 - abs(rm_hue - c.h)) - if(hd <= dh): - h = True - if(us): - if(rmcolor.s - ds < c.s < rmcolor.s + ds): - s = True - if(uv): - if(rmcolor.v - dv < c.v < rmcolor.v + dv): - v = True - - a = False - if(uh and not us and not uv): - if(h): - a = True - elif(not uh and us and not uv): - if(s): - a = True - elif(not uh and not us and uv): - if(v): - a = True - elif(uh and us and not uv): - if(h and s): - a = True - elif(not uh and us and uv): - if(s and v): - a = True - elif(uh and not us and uv): - if(h and v): - a = True - elif(uh and us and uv): - if(h and s and v): - a = True - else: - pass - if(a): - indexes.append(p['index']) - - log("selected: {} points".format(len(indexes)), 1) - - if(len(indexes) == 0): - # self.report({'ERROR'}, "Nothing selected.") - self.report({'INFO'}, "Nothing selected.") - else: - pcv.filter_remove_color_selection = True - c = PCVManager.cache[pcv.uuid] - c['selection_indexes'] = indexes - - context.area.tag_redraw() - - _d = datetime.timedelta(seconds=time.time() - _t) - log("completed in {}.".format(_d), 1) - - return {'FINISHED'} - - -class PCV_OT_filter_remove_color_deselect(Operator): - bl_idname = "point_cloud_visualizer.filter_remove_color_deselect" - bl_label = "Deselect" - bl_description = "Deselect points" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - if(pcv.filter_remove_color_selection): - if('selection_indexes' in v.keys()): - ok = True - return ok - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - c = PCVManager.cache[pcv.uuid] - - pcv.filter_remove_color_selection = False - del c['selection_indexes'] - - context.area.tag_redraw() - - return {'FINISHED'} - - -class PCV_OT_filter_remove_color_delete_selected(Operator): - bl_idname = "point_cloud_visualizer.filter_remove_color_delete_selected" - bl_label = "Delete Selected" - bl_description = "Remove selected points" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - if(pcv.filter_remove_color_selection): - if('selection_indexes' in v.keys()): - ok = True - return ok - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - c = PCVManager.cache[pcv.uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - indexes = c['selection_indexes'] - vs = np.delete(vs, indexes, axis=0, ) - ns = np.delete(ns, indexes, axis=0, ) - cs = np.delete(cs, indexes, axis=0, ) - - PCVManager.update(pcv.uuid, vs, ns, cs, ) - - pcv.filter_remove_color_selection = False - del c['selection_indexes'] - - return {'FINISHED'} - - -class PCV_OT_edit_start(Operator): - bl_idname = "point_cloud_visualizer.edit_start" - bl_label = "Start" - bl_description = "Start edit mode, create helper object and switch to it" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - ok = True - return ok - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - c = PCVManager.cache[pcv.uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - - # ensure object mode - bpy.ops.object.mode_set(mode='OBJECT') - - # prepare mesh - bm = bmesh.new() - for v in vs: - bm.verts.new(v) - bm.verts.ensure_lookup_table() - l = bm.verts.layers.int.new('pcv_indexes') - for i in range(len(vs)): - bm.verts[i][l] = i - # add mesh to scene, activate - nm = 'pcv_edit_mesh_{}'.format(pcv.uuid) - me = bpy.data.meshes.new(nm) - bm.to_mesh(me) - bm.free() - o = bpy.data.objects.new(nm, me) - view_layer = context.view_layer - collection = view_layer.active_layer_collection.collection - collection.objects.link(o) - p = context.object - o.parent = p - o.matrix_world = p.matrix_world.copy() - bpy.ops.object.select_all(action='DESELECT') - o.select_set(True) - view_layer.objects.active = o - # and set edit mode - bpy.ops.object.mode_set(mode='EDIT') - # set vertex select mode.. - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT', ) - - o.point_cloud_visualizer.edit_is_edit_uuid = pcv.uuid - o.point_cloud_visualizer.edit_is_edit_mesh = True - pcv.edit_initialized = True - pcv.edit_pre_edit_alpha = pcv.global_alpha - pcv.global_alpha = pcv.edit_overlay_alpha - pcv.edit_pre_edit_display = pcv.display_percent - pcv.display_percent = 100.0 - pcv.edit_pre_edit_size = pcv.point_size - pcv.point_size = pcv.edit_overlay_size - - return {'FINISHED'} - - -class PCV_OT_edit_update(Operator): - bl_idname = "point_cloud_visualizer.edit_update" - bl_label = "Update" - bl_description = "Update displayed cloud from edited mesh" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - if(pcv.edit_is_edit_mesh): - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.edit_is_edit_uuid): - if(v['ready']): - if(v['draw']): - if(context.mode == 'EDIT_MESH'): - ok = True - return ok - - def execute(self, context): - # get current data - uuid = context.object.point_cloud_visualizer.edit_is_edit_uuid - c = PCVManager.cache[uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - # extract edited data - o = context.object - bm = bmesh.from_edit_mesh(o.data) - bm.verts.ensure_lookup_table() - l = bm.verts.layers.int['pcv_indexes'] - edit_vs = [] - edit_indexes = [] - for v in bm.verts: - edit_vs.append(v.co.to_tuple()) - edit_indexes.append(v[l]) - # combine - u_vs = [] - u_ns = [] - u_cs = [] - for i, indx in enumerate(edit_indexes): - u_vs.append(edit_vs[i]) - u_ns.append(ns[indx]) - u_cs.append(cs[indx]) - # display - vs = np.array(u_vs, dtype=np.float32, ) - ns = np.array(u_ns, dtype=np.float32, ) - cs = np.array(u_cs, dtype=np.float32, ) - PCVManager.update(uuid, vs, ns, cs, ) - # update indexes - bm.verts.ensure_lookup_table() - l = bm.verts.layers.int['pcv_indexes'] - for i in range(len(vs)): - bm.verts[i][l] = i - - return {'FINISHED'} - - -class PCV_OT_edit_end(Operator): - bl_idname = "point_cloud_visualizer.edit_end" - bl_label = "End" - bl_description = "Update displayed cloud from edited mesh, stop edit mode and remove helper object" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - if(pcv.edit_is_edit_mesh): - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.edit_is_edit_uuid): - if(v['ready']): - if(v['draw']): - if(context.mode == 'EDIT_MESH'): - ok = True - return ok - - def execute(self, context): - # update - bpy.ops.point_cloud_visualizer.edit_update() - - # cleanup - bpy.ops.object.mode_set(mode='EDIT') - o = context.object - p = o.parent - me = o.data - view_layer = context.view_layer - collection = view_layer.active_layer_collection.collection - collection.objects.unlink(o) - bpy.data.objects.remove(o) - bpy.data.meshes.remove(me) - # go back - bpy.ops.object.select_all(action='DESELECT') - p.select_set(True) - view_layer.objects.active = p - - p.point_cloud_visualizer.edit_initialized = False - p.point_cloud_visualizer.global_alpha = p.point_cloud_visualizer.edit_pre_edit_alpha - p.point_cloud_visualizer.display_percent = p.point_cloud_visualizer.edit_pre_edit_display - p.point_cloud_visualizer.point_size = p.point_cloud_visualizer.edit_pre_edit_size - - return {'FINISHED'} - - -class PCV_OT_edit_cancel(Operator): - bl_idname = "point_cloud_visualizer.edit_cancel" - bl_label = "Cancel" - bl_description = "Stop edit mode, try to remove helper object and reload original point cloud" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - if(pcv.edit_initialized): - return True - return False - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - po = context.object - nm = 'pcv_edit_mesh_{}'.format(pcv.uuid) - view_layer = context.view_layer - collection = view_layer.active_layer_collection.collection - for o in po.children: - if(o.name == nm): - me = o.data - collection.objects.unlink(o) - bpy.data.objects.remove(o) - bpy.data.meshes.remove(me) - break - - pcv.edit_initialized = False - pcv.global_alpha = pcv.edit_pre_edit_alpha - pcv.edit_pre_edit_alpha = 0.5 - pcv.display_percent = pcv.edit_pre_edit_display - pcv.edit_pre_edit_display = 100.0 - pcv.point_size = pcv.edit_pre_edit_size - pcv.edit_pre_edit_size = 3 - - # also beware, this changes uuid - bpy.ops.point_cloud_visualizer.reload() - - return {'FINISHED'} - - -class PCV_OT_filter_merge(Operator): - bl_idname = "point_cloud_visualizer.filter_merge" - bl_label = "Merge With Other PLY" - bl_description = "Merge with other ply file" - - filename_ext = ".ply" - filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}, ) - filepath: StringProperty(name="File Path", default="", description="", maxlen=1024, subtype='FILE_PATH', ) - order = ["filepath", ] - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - - pcv = context.object.point_cloud_visualizer - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - ok = True - return ok - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - - filepath = self.filepath - h, t = os.path.split(filepath) - n, e = os.path.splitext(t) - if(e != '.ply'): - self.report({'ERROR'}, "File at '{}' seems not to be a PLY file.".format(filepath)) - return {'CANCELLED'} - - points = [] - try: - points = PlyPointCloudReader(filepath).points - except Exception as e: - self.report({'ERROR'}, str(e)) - return {'CANCELLED'} - if(len(points) == 0): - self.report({'ERROR'}, "No vertices loaded from file at {}".format(filepath)) - return {'CANCELLED'} - - if(not set(('x', 'y', 'z')).issubset(points.dtype.names)): - # this is very unlikely.. - self.report({'ERROR'}, "Loaded data seems to miss vertex locations.") - return False - normals = True - if(not set(('nx', 'ny', 'nz')).issubset(points.dtype.names)): - normals = False - pcv.has_normals = normals - if(not pcv.has_normals): - pcv.illumination = False - vcols = True - if(not set(('red', 'green', 'blue')).issubset(points.dtype.names)): - vcols = False - pcv.has_vcols = vcols - - vs = np.column_stack((points['x'], points['y'], points['z'], )) - - if(normals): - ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) - else: - n = len(points) - ns = np.column_stack((np.full(n, 0.0, dtype=np.float32, ), - np.full(n, 0.0, dtype=np.float32, ), - np.full(n, 1.0, dtype=np.float32, ), )) - - if(vcols): - preferences = bpy.context.preferences - addon_prefs = preferences.addons[__name__].preferences - if(addon_prefs.convert_16bit_colors and points['red'].dtype == 'uint16'): - r8 = (points['red'] / 256).astype('uint8') - g8 = (points['green'] / 256).astype('uint8') - b8 = (points['blue'] / 256).astype('uint8') - if(addon_prefs.gamma_correct_16bit_colors): - cs = np.column_stack(((r8 / 255) ** (1 / 2.2), - (g8 / 255) ** (1 / 2.2), - (b8 / 255) ** (1 / 2.2), - np.ones(len(points), dtype=float, ), )) - else: - cs = np.column_stack((r8 / 255, g8 / 255, b8 / 255, np.ones(len(points), dtype=float, ), )) - cs = cs.astype(np.float32) - else: - # 'uint8' - cs = np.column_stack((points['red'] / 255, points['green'] / 255, points['blue'] / 255, np.ones(len(points), dtype=float, ), )) - cs = cs.astype(np.float32) - else: - n = len(points) - preferences = bpy.context.preferences - addon_prefs = preferences.addons[__name__].preferences - col = addon_prefs.default_vertex_color[:] - col = tuple([c ** (1 / 2.2) for c in col]) + (1.0, ) - cs = np.column_stack((np.full(n, col[0], dtype=np.float32, ), - np.full(n, col[1], dtype=np.float32, ), - np.full(n, col[2], dtype=np.float32, ), - np.ones(n, dtype=np.float32, ), )) - - # append - c = PCVManager.cache[pcv.uuid] - ovs = c['vertices'] - ons = c['normals'] - ocs = c['colors'] - vs = np.concatenate((ovs, vs, )) - ns = np.concatenate((ons, ns, )) - cs = np.concatenate((ocs, cs, )) - - preferences = bpy.context.preferences - addon_prefs = preferences.addons[__name__].preferences - if(addon_prefs.shuffle_points): - l = len(vs) - dt = [('x', '= 0): - return True - return False - - # if(debug_mode()): - # import cProfile - # import pstats - # import io - # pr = cProfile.Profile() - # pr.enable() - - indexes = [] - prgs = Progress(len(vs), indent=1, prefix="> ") - for i, v in enumerate(vs): - prgs.step() - vv = Vector(v) - ''' - inside1 = is_point_inside_mesh_v1(vv, target, ) - inside2 = is_point_inside_mesh_v2(vv, target, ) - inside3 = is_point_inside_mesh_v3(vv, target, ) - # intersect - if(not inside1 and not inside2 and not inside3): - indexes.append(i) - # # exclude - # if(inside1 and inside2 and inside3): - # indexes.append(i) - ''' - - in_bb = is_in_bound_box(v) - if(not in_bb): - # is not in bounds i can skip completely - indexes.append(i) - continue - - inside3 = is_point_inside_mesh_v3(vv, target, ) - if(not inside3): - indexes.append(i) - - # if(debug_mode()): - # pr.disable() - # s = io.StringIO() - # sortby = 'cumulative' - # ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - # ps.print_stats() - # print(s.getvalue()) - - c = PCVManager.cache[pcv.uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - vs = np.delete(vs, indexes, axis=0, ) - ns = np.delete(ns, indexes, axis=0, ) - cs = np.delete(cs, indexes, axis=0, ) + log("postprocessing..", 1) + # split back + vs = np.column_stack((a['x'], a['y'], a['z'], )) + ns = np.column_stack((a['nx'], a['ny'], a['nz'], )) + cs = np.column_stack((a['red'], a['green'], a['blue'], a['alpha'], )) + vs = vs.astype(np.float32) + ns = ns.astype(np.float32) + cs = cs.astype(np.float32) - log("removed: {} points".format(len(indexes)), 1) + # unapply parent matrix to points + m = c['object'].matrix_world.copy() + m = m.inverted() + vs, ns = apply_matrix(m, vs, ns, ) + vs = vs.astype(np.float32) + ns = ns.astype(np.float32) # cleanup + if(pcv.filter_project_colorize): + bm.free() + # owner.to_mesh_clear() + collection.objects.unlink(target) bpy.data.objects.remove(target) bpy.data.meshes.remove(target_mesh) @@ -8361,16 +6764,24 @@ def is_point_inside_mesh_v3(p, o, ): pcv = context.object.point_cloud_visualizer PCVManager.update(pcv.uuid, vs, ns, cs, ) + # if(debug_mode()): + # pr.disable() + # s = io.StringIO() + # sortby = 'cumulative' + # ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + # ps.print_stats() + # print(s.getvalue()) + _d = datetime.timedelta(seconds=time.time() - _t) log("completed in {}.".format(_d), 1) return {'FINISHED'} -class PCV_OT_filter_boolean_exclude(Operator): - bl_idname = "point_cloud_visualizer.filter_boolean_exclude" - bl_label = "Exclude" - bl_description = "" +class PCV_OT_filter_remove_color(Operator): + bl_idname = "point_cloud_visualizer.filter_remove_color" + bl_label = "Select Color" + bl_description = "Select points with exact/similar color" @classmethod def poll(cls, context): @@ -8383,189 +6794,121 @@ def poll(cls, context): if(v['uuid'] == pcv.uuid): if(v['ready']): if(v['draw']): - o = pcv.filter_boolean_object - if(o is not None): - if(o.type in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): - ok = True + ok = True return ok def execute(self, context): - log("Exclude:", 0) + log("Remove Color:", 0) _t = time.time() pcv = context.object.point_cloud_visualizer - o = pcv.filter_boolean_object - if(o is None): - raise Exception() - - def apply_matrix(vs, ns, matrix, ): - matrot = matrix.decompose()[1].to_matrix().to_4x4() - dtv = vs.dtype - dtn = ns.dtype - rvs = np.zeros(vs.shape, dtv) - rns = np.zeros(ns.shape, dtn) - for i in range(len(vs)): - co = matrix @ Vector(vs[i]) - no = matrot @ Vector(ns[i]) - rvs[i] = np.array(co.to_tuple(), dtv) - rns[i] = np.array(no.to_tuple(), dtn) - return rvs, rns - + # cache item c = PCVManager.cache[pcv.uuid] vs = c['vertices'] ns = c['normals'] cs = c['colors'] + # join to indexed points + l = len(vs) + dt = [('x', '= 0): - return True - return False + # take half of the value because 1/2 <- v -> 1/2, plus and minus => full range + dh = pcv.filter_remove_color_delta_hue / 2 + # only for hue, because i take in consideration its radial nature + ds = pcv.filter_remove_color_delta_saturation + dv = pcv.filter_remove_color_delta_value + uh = pcv.filter_remove_color_delta_hue_use + us = pcv.filter_remove_color_delta_saturation_use + uv = pcv.filter_remove_color_delta_value_use - indexes = [] - prgs = Progress(len(vs), indent=1, prefix="> ") - for i, v in enumerate(vs): - prgs.step() - vv = Vector(v) - ''' - inside1 = is_point_inside_mesh_v1(vv, target, ) - inside2 = is_point_inside_mesh_v2(vv, target, ) - inside3 = is_point_inside_mesh_v3(vv, target, ) - # # intersect - # if(not inside1 and not inside2 and not inside3): - # indexes.append(i) - # exclude - if(inside1 and inside2 and inside3): - indexes.append(i) - ''' - - in_bb = is_in_bound_box(v) - if(in_bb): - # indexes.append(i) - # continue - - # is in bound so i can check further - inside3 = is_point_inside_mesh_v3(vv, target, ) - if(inside3): - indexes.append(i) + prgr = Progress(len(points), 1) + for p in points: + prgr.step() + # get point color + c = Color((p['red'], p['green'], p['blue'])) + # check for more or less same color, a few decimals should be more than enough, ply should have 8bit colors + fpr = 5 + same = (round(c.r, fpr) == round(rmcolor.r, fpr), + round(c.g, fpr) == round(rmcolor.g, fpr), + round(c.b, fpr) == round(rmcolor.b, fpr)) + if(all(same)): + indexes.append(p['index']) + continue - # inside3 = is_point_inside_mesh_v3(vv, target, ) - # if(inside3): - # indexes.append(i) - - c = PCVManager.cache[pcv.uuid] - vs = c['vertices'] - ns = c['normals'] - cs = c['colors'] - vs = np.delete(vs, indexes, axis=0, ) - ns = np.delete(ns, indexes, axis=0, ) - cs = np.delete(cs, indexes, axis=0, ) + # check + h = False + s = False + v = False + if(uh): + rm_hue = rmcolor.h + hd = min(abs(rm_hue - c.h), 1.0 - abs(rm_hue - c.h)) + if(hd <= dh): + h = True + if(us): + if(rmcolor.s - ds < c.s < rmcolor.s + ds): + s = True + if(uv): + if(rmcolor.v - dv < c.v < rmcolor.v + dv): + v = True + + a = False + if(uh and not us and not uv): + if(h): + a = True + elif(not uh and us and not uv): + if(s): + a = True + elif(not uh and not us and uv): + if(v): + a = True + elif(uh and us and not uv): + if(h and s): + a = True + elif(not uh and us and uv): + if(s and v): + a = True + elif(uh and not us and uv): + if(h and v): + a = True + elif(uh and us and uv): + if(h and s and v): + a = True + else: + pass + if(a): + indexes.append(p['index']) - log("removed: {} points".format(len(indexes)), 1) + log("selected: {} points".format(len(indexes)), 1) - # cleanup - collection.objects.unlink(target) - bpy.data.objects.remove(target) - bpy.data.meshes.remove(target_mesh) - o.to_mesh_clear() + if(len(indexes) == 0): + # self.report({'ERROR'}, "Nothing selected.") + self.report({'INFO'}, "Nothing selected.") + else: + pcv.filter_remove_color_selection = True + c = PCVManager.cache[pcv.uuid] + c['selection_indexes'] = indexes - # put to cache.. - pcv = context.object.point_cloud_visualizer - PCVManager.update(pcv.uuid, vs, ns, cs, ) + context.area.tag_redraw() _d = datetime.timedelta(seconds=time.time() - _t) log("completed in {}.".format(_d), 1) @@ -8573,10 +6916,10 @@ def is_point_inside_mesh_v3(p, o, ): return {'FINISHED'} -class PCV_OT_reload(Operator): - bl_idname = "point_cloud_visualizer.reload" - bl_label = "Reload" - bl_description = "Reload points from file" +class PCV_OT_filter_remove_color_deselect(Operator): + bl_idname = "point_cloud_visualizer.filter_remove_color_deselect" + bl_label = "Deselect" + bl_description = "Deselect points" @classmethod def poll(cls, context): @@ -8584,42 +6927,72 @@ def poll(cls, context): return False pcv = context.object.point_cloud_visualizer - # if(pcv.filepath != '' and pcv.uuid != '' and not pcv.runtime): - if(pcv.filepath != '' and pcv.uuid != ''): - return True - return False + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + if(pcv.filter_remove_color_selection): + if('selection_indexes' in v.keys()): + ok = True + return ok def execute(self, context): pcv = context.object.point_cloud_visualizer + c = PCVManager.cache[pcv.uuid] - draw = False + pcv.filter_remove_color_selection = False + del c['selection_indexes'] + + context.area.tag_redraw() + + return {'FINISHED'} + + +class PCV_OT_filter_remove_color_delete_selected(Operator): + bl_idname = "point_cloud_visualizer.filter_remove_color_delete_selected" + bl_label = "Delete Selected" + bl_description = "Remove selected points" + + @classmethod + def poll(cls, context): + if(context.object is None): + return False + + pcv = context.object.point_cloud_visualizer + ok = False for k, v in PCVManager.cache.items(): if(v['uuid'] == pcv.uuid): if(v['ready']): if(v['draw']): - draw = True - bpy.ops.point_cloud_visualizer.erase() + if(pcv.filter_remove_color_selection): + if('selection_indexes' in v.keys()): + ok = True + return ok + + def execute(self, context): + pcv = context.object.point_cloud_visualizer + c = PCVManager.cache[pcv.uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] + indexes = c['selection_indexes'] + vs = np.delete(vs, indexes, axis=0, ) + ns = np.delete(ns, indexes, axis=0, ) + cs = np.delete(cs, indexes, axis=0, ) - if(pcv.runtime): - c = PCVManager.cache[pcv.uuid] - points = c['points'] - vs = np.column_stack((points['x'], points['y'], points['z'], )) - ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) - cs = c['colors_original'] - PCVManager.update(pcv.uuid, vs, ns, cs, ) - else: - bpy.ops.point_cloud_visualizer.load_ply_to_cache(filepath=pcv.filepath) + PCVManager.update(pcv.uuid, vs, ns, cs, ) - if(draw): - bpy.ops.point_cloud_visualizer.draw() + pcv.filter_remove_color_selection = False + del c['selection_indexes'] return {'FINISHED'} -class PCV_OT_sequence_preload(Operator): - bl_idname = "point_cloud_visualizer.sequence_preload" - bl_label = "Preload Sequence" - bl_description = "Preload sequence of PLY files. Files should be numbered starting at 1. Missing files in sequence will be skipped." +class PCV_OT_edit_start(Operator): + bl_idname = "point_cloud_visualizer.edit_start" + bl_label = "Start" + bl_description = "Start edit mode, create helper object and switch to it" @classmethod def poll(cls, context): @@ -8627,8 +7000,6 @@ def poll(cls, context): return False pcv = context.object.point_cloud_visualizer - if(pcv.uuid in PCVSequence.cache.keys()): - return False ok = False for k, v in PCVManager.cache.items(): if(v['uuid'] == pcv.uuid): @@ -8638,129 +7009,60 @@ def poll(cls, context): return ok def execute(self, context): - log('Preload Sequence..') pcv = context.object.point_cloud_visualizer + c = PCVManager.cache[pcv.uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] - # pcv.sequence_enabled = True - - dirpath = os.path.dirname(pcv.filepath) - files = [] - for (p, ds, fs) in os.walk(dirpath): - files.extend(fs) - break - - fs = [f for f in files if f.lower().endswith('.ply')] - f = os.path.split(pcv.filepath)[1] - - pattern = re.compile(r'(\d+)(?!.*(\d+))') - - m = re.search(pattern, f, ) - if(m is not None): - prefix = f[:m.start()] - suffix = f[m.end():] - else: - self.report({'ERROR'}, 'Filename does not contain any sequence number') - return {'CANCELLED'} - - sel = [] - - for n in fs: - m = re.search(pattern, n, ) - if(m is not None): - # some numbers present, lets compare with selected file prefix/suffix - pre = n[:m.start()] - if(pre == prefix): - # prefixes match - suf = n[m.end():] - if(suf == suffix): - # suffixes match, extract number - si = n[m.start():m.end()] - try: - # try convert it to integer - i = int(si) - # and store as selected file - sel.append((i, n)) - except ValueError: - pass - - # sort by sequence number - sel.sort() - # fabricate list with missing sequence numbers as None - sequence = [[None] for i in range(sel[-1][0])] - for i, n in sel: - sequence[i - 1] = (i, n) - for i in range(len(sequence)): - if(sequence[i][0] is None): - sequence[i] = [i, None] - - log('found files:', 1) - for i, n in sequence: - log('{}: {}'.format(i, n), 2) - - log('preloading..', 1) - # this is our sequence with matching filenames, sorted by numbers with missing as None, now load it all.. - cache = [] - for i, n in sequence: - if(n is not None): - p = os.path.join(dirpath, n) - points = [] - try: - points = PlyPointCloudReader(p).points - except Exception as e: - self.report({'ERROR'}, str(e)) - if(len(points) == 0): - self.report({'ERROR'}, "No vertices loaded from file at {}".format(p)) - else: - if(not set(('x', 'y', 'z')).issubset(points.dtype.names)): - self.report({'ERROR'}, "Loaded data seems to miss vertex locations.") - return {'CANCELLED'} - - vs = np.column_stack((points['x'], points['y'], points['z'], )) - vs = vs.astype(np.float32) - - if(not set(('nx', 'ny', 'nz')).issubset(points.dtype.names)): - ns = None - else: - ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) - ns = ns.astype(np.float32) - if(not set(('red', 'green', 'blue')).issubset(points.dtype.names)): - cs = None - else: - cs = np.column_stack((points['red'] / 255, points['green'] / 255, points['blue'] / 255, np.ones(len(points), dtype=float, ), )) - cs = cs.astype(np.float32) - - cache.append({'index': i, - 'name': n, - 'path': p, - 'vs': vs, - 'ns': ns, - 'cs': cs, - 'points': points, }) - - log('...', 1) - log('loaded {} item(s)'.format(len(cache)), 1) - log('initializing..', 1) - - PCVSequence.init() + # ensure object mode + bpy.ops.object.mode_set(mode='OBJECT') - ci = {'data': cache, - 'uuid': pcv.uuid, - 'pcv': pcv, } - PCVSequence.cache[pcv.uuid] = ci + # prepare mesh + bm = bmesh.new() + for v in vs: + bm.verts.new(v) + bm.verts.ensure_lookup_table() + l = bm.verts.layers.int.new('pcv_indexes') + for i in range(len(vs)): + bm.verts[i][l] = i + # add mesh to scene, activate + nm = 'pcv_edit_mesh_{}'.format(pcv.uuid) + me = bpy.data.meshes.new(nm) + bm.to_mesh(me) + bm.free() + o = bpy.data.objects.new(nm, me) + view_layer = context.view_layer + collection = view_layer.active_layer_collection.collection + collection.objects.link(o) + p = context.object + o.parent = p + o.matrix_world = p.matrix_world.copy() + bpy.ops.object.select_all(action='DESELECT') + o.select_set(True) + view_layer.objects.active = o + # and set edit mode + bpy.ops.object.mode_set(mode='EDIT') + # set vertex select mode.. + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT', ) - log('force frame update..', 1) - sc = bpy.context.scene - cf = sc.frame_current - sc.frame_current = cf - log('done.', 1) + o.point_cloud_visualizer.edit_is_edit_uuid = pcv.uuid + o.point_cloud_visualizer.edit_is_edit_mesh = True + pcv.edit_initialized = True + pcv.edit_pre_edit_alpha = pcv.global_alpha + pcv.global_alpha = pcv.edit_overlay_alpha + pcv.edit_pre_edit_display = pcv.display_percent + pcv.display_percent = 100.0 + pcv.edit_pre_edit_size = pcv.point_size + pcv.point_size = pcv.edit_overlay_size return {'FINISHED'} -class PCV_OT_sequence_clear(Operator): - bl_idname = "point_cloud_visualizer.sequence_clear" - bl_label = "Clear Sequence" - bl_description = "Clear preloaded sequence cache and reset all" +class PCV_OT_edit_update(Operator): + bl_idname = "point_cloud_visualizer.edit_update" + bl_label = "Update" + bl_description = "Update displayed cloud from edited mesh" @classmethod def poll(cls, context): @@ -8768,277 +7070,387 @@ def poll(cls, context): return False pcv = context.object.point_cloud_visualizer - if(pcv.uuid in PCVSequence.cache.keys()): - return True - return False - # ok = False - # for k, v in PCVManager.cache.items(): - # if(v['uuid'] == pcv.uuid): - # if(v['ready']): - # if(v['draw']): - # ok = True - # return ok + ok = False + if(pcv.edit_is_edit_mesh): + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.edit_is_edit_uuid): + if(v['ready']): + if(v['draw']): + if(context.mode == 'EDIT_MESH'): + ok = True + return ok def execute(self, context): - pcv = context.object.point_cloud_visualizer - - del PCVSequence.cache[pcv.uuid] - if(len(PCVSequence.cache.items()) == 0): - PCVSequence.deinit() - - # c = PCVManager.cache[pcv.uuid] - # vs = c['vertices'] - # ns = c['normals'] - # cs = c['colors'] - # PCVManager.update(pcv.uuid, vs, ns, cs, ) - - bpy.ops.point_cloud_visualizer.reload() + # get current data + uuid = context.object.point_cloud_visualizer.edit_is_edit_uuid + c = PCVManager.cache[uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] + # extract edited data + o = context.object + bm = bmesh.from_edit_mesh(o.data) + bm.verts.ensure_lookup_table() + l = bm.verts.layers.int['pcv_indexes'] + edit_vs = [] + edit_indexes = [] + for v in bm.verts: + edit_vs.append(v.co.to_tuple()) + edit_indexes.append(v[l]) + # combine + u_vs = [] + u_ns = [] + u_cs = [] + for i, indx in enumerate(edit_indexes): + u_vs.append(edit_vs[i]) + u_ns.append(ns[indx]) + u_cs.append(cs[indx]) + # display + vs = np.array(u_vs, dtype=np.float32, ) + ns = np.array(u_ns, dtype=np.float32, ) + cs = np.array(u_cs, dtype=np.float32, ) + PCVManager.update(uuid, vs, ns, cs, ) + # update indexes + bm.verts.ensure_lookup_table() + l = bm.verts.layers.int['pcv_indexes'] + for i in range(len(vs)): + bm.verts[i][l] = i return {'FINISHED'} -class PCV_OT_seq_init(Operator): - bl_idname = "point_cloud_visualizer.seq_init" - bl_label = "seq_init" - - def execute(self, context): - PCVSequence.init() - context.area.tag_redraw() - return {'FINISHED'} - - -class PCV_OT_seq_deinit(Operator): - bl_idname = "point_cloud_visualizer.seq_deinit" - bl_label = "seq_deinit" - - def execute(self, context): - PCVSequence.deinit() - context.area.tag_redraw() - return {'FINISHED'} - - -class PCV_OT_generate_point_cloud(Operator): - bl_idname = "point_cloud_visualizer.generate_from_mesh" - bl_label = "Generate" - bl_description = "Generate colored point cloud from mesh (or object convertible to mesh)" +class PCV_OT_edit_end(Operator): + bl_idname = "point_cloud_visualizer.edit_end" + bl_label = "End" + bl_description = "Update displayed cloud from edited mesh, stop edit mode and remove helper object" @classmethod def poll(cls, context): if(context.object is None): return False - # pcv = context.object.point_cloud_visualizer - # if(pcv.uuid in PCVSequence.cache.keys()): - # return False - # ok = False - # for k, v in PCVManager.cache.items(): - # if(v['uuid'] == pcv.uuid): - # if(v['ready']): - # if(v['draw']): - # ok = True - # return ok - - return True + pcv = context.object.point_cloud_visualizer + ok = False + if(pcv.edit_is_edit_mesh): + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.edit_is_edit_uuid): + if(v['ready']): + if(v['draw']): + if(context.mode == 'EDIT_MESH'): + ok = True + return ok def execute(self, context): - log("Generate From Mesh:", 0) - _t = time.time() - - o = context.object - - if(o.type not in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): - self.report({'ERROR'}, "Object does not have geometry data.") - return {'CANCELLED'} - - pcv = o.point_cloud_visualizer - - if(pcv.generate_source not in ('SURFACE', 'VERTICES', 'PARTICLES', )): - self.report({'ERROR'}, "Source not implemented.") - return {'CANCELLED'} - - n = pcv.generate_number_of_points - r = random.Random(pcv.generate_seed) - - if(pcv.generate_colors in ('CONSTANT', 'UVTEX', 'VCOLS', 'GROUP_MONO', 'GROUP_COLOR', )): - if(o.type in ('CURVE', 'SURFACE', 'FONT', ) and pcv.generate_colors != 'CONSTANT'): - self.report({'ERROR'}, "Object type does not support UV textures, vertex colors or vertex groups.") - return {'CANCELLED'} - else: - self.report({'ERROR'}, "Color generation not implemented.") - return {'CANCELLED'} - - if(o.type != 'MESH'): - vcols = None - uvtex = None - vgroup = None - else: - # all of following should return None is not available, at least for mesh object - vcols = o.data.vertex_colors.active - uvtex = o.data.uv_layers.active - vgroup = o.vertex_groups.active - - if(pcv.generate_source == 'VERTICES'): - try: - sampler = PCVVertexSampler(context, o, - colorize=pcv.generate_colors, - constant_color=pcv.generate_constant_color, - vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) - except Exception as e: - self.report({'ERROR'}, str(e), ) - return {'CANCELLED'} - elif(pcv.generate_source == 'SURFACE'): - if(pcv.generate_algorithm == 'WEIGHTED_RANDOM_IN_TRIANGLE'): - try: - sampler = PCVTriangleSurfaceSampler(context, o, n, r, - colorize=pcv.generate_colors, - constant_color=pcv.generate_constant_color, - vcols=vcols, uvtex=uvtex, vgroup=vgroup, - exact_number_of_points=pcv.generate_exact_number_of_points, ) - except Exception as e: - self.report({'ERROR'}, str(e), ) - return {'CANCELLED'} - elif(pcv.generate_algorithm == 'POISSON_DISK_SAMPLING'): - try: - sampler = PCVPoissonDiskSurfaceSampler(context, o, r, minimal_distance=pcv.generate_minimal_distance, - sampling_exponent=pcv.generate_sampling_exponent, - colorize=pcv.generate_colors, - constant_color=pcv.generate_constant_color, - vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) - except Exception as e: - self.report({'ERROR'}, str(e), ) - return {'CANCELLED'} - else: - self.report({'ERROR'}, "Algorithm not implemented.") - return {'CANCELLED'} - - elif(pcv.generate_source == 'PARTICLES'): - try: - alive_only = True - if(pcv.generate_source_psys == 'ALL'): - alive_only = False - sampler = PCVParticleSystemSampler(context, o, alive_only=alive_only, - colorize=pcv.generate_colors, - constant_color=pcv.generate_constant_color, - vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) - except Exception as e: - self.report({'ERROR'}, str(e), ) - return {'CANCELLED'} - else: - self.report({'ERROR'}, "Source type not implemented.") - return {'CANCELLED'} - - vs = sampler.vs - ns = sampler.ns - cs = sampler.cs - - log("generated {} points.".format(len(vs)), 1) - - ok = False - for k, v in PCVManager.cache.items(): - if(v['uuid'] == pcv.uuid): - if(v['ready']): - if(v['draw']): - ok = True - if(ok): - bpy.ops.point_cloud_visualizer.erase() - - c = PCVControl(o) - c.draw(vs, ns, cs) + # update + bpy.ops.point_cloud_visualizer.edit_update() - if(debug_mode()): - o.display_type = 'BOUNDS' + # cleanup + bpy.ops.object.mode_set(mode='EDIT') + o = context.object + p = o.parent + me = o.data + view_layer = context.view_layer + collection = view_layer.active_layer_collection.collection + collection.objects.unlink(o) + bpy.data.objects.remove(o) + bpy.data.meshes.remove(me) + # go back + bpy.ops.object.select_all(action='DESELECT') + p.select_set(True) + view_layer.objects.active = p - _d = datetime.timedelta(seconds=time.time() - _t) - log("completed in {}.".format(_d), 1) + p.point_cloud_visualizer.edit_initialized = False + p.point_cloud_visualizer.global_alpha = p.point_cloud_visualizer.edit_pre_edit_alpha + p.point_cloud_visualizer.display_percent = p.point_cloud_visualizer.edit_pre_edit_display + p.point_cloud_visualizer.point_size = p.point_cloud_visualizer.edit_pre_edit_size return {'FINISHED'} -class PCV_OT_reset_runtime(Operator): - bl_idname = "point_cloud_visualizer.reset_runtime" - bl_label = "Reset Runtime" - bl_description = "Reset PCV to its default state if in runtime mode (displayed data is set with python and not with ui)" +class PCV_OT_edit_cancel(Operator): + bl_idname = "point_cloud_visualizer.edit_cancel" + bl_label = "Cancel" + bl_description = "Stop edit mode, try to remove helper object and reload original point cloud" @classmethod def poll(cls, context): if(context.object is None): return False + pcv = context.object.point_cloud_visualizer - if(pcv.runtime): + if(pcv.edit_initialized): return True return False def execute(self, context): - o = context.object - c = PCVControl(o) - c.erase() - c.reset() + pcv = context.object.point_cloud_visualizer + po = context.object + nm = 'pcv_edit_mesh_{}'.format(pcv.uuid) + view_layer = context.view_layer + collection = view_layer.active_layer_collection.collection + for o in po.children: + if(o.name == nm): + me = o.data + collection.objects.unlink(o) + bpy.data.objects.remove(o) + bpy.data.meshes.remove(me) + break + + pcv.edit_initialized = False + pcv.global_alpha = pcv.edit_pre_edit_alpha + pcv.edit_pre_edit_alpha = 0.5 + pcv.display_percent = pcv.edit_pre_edit_display + pcv.edit_pre_edit_display = 100.0 + pcv.point_size = pcv.edit_pre_edit_size + pcv.edit_pre_edit_size = 3 + + # also beware, this changes uuid + bpy.ops.point_cloud_visualizer.reload() + return {'FINISHED'} -class PCV_OT_generate_volume_point_cloud(Operator): - bl_idname = "point_cloud_visualizer.generate_volume_from_mesh" - bl_label = "Generate Volume" - bl_description = "Generate colored point cloud in mesh (or object convertible to mesh) volume" +class PCV_OT_filter_merge(Operator): + bl_idname = "point_cloud_visualizer.filter_merge" + bl_label = "Merge With Other PLY" + bl_description = "Merge with other ply file" + + filename_ext = ".ply" + filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}, ) + filepath: StringProperty(name="File Path", default="", description="", maxlen=1024, subtype='FILE_PATH', ) + order = ["filepath", ] @classmethod def poll(cls, context): if(context.object is None): return False - # pcv = context.object.point_cloud_visualizer - # if(pcv.uuid in PCVSequence.cache.keys()): - # return False - # ok = False - # for k, v in PCVManager.cache.items(): - # if(v['uuid'] == pcv.uuid): - # if(v['ready']): - # if(v['draw']): - # ok = True - # return ok + pcv = context.object.point_cloud_visualizer + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + ok = True + return ok + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + pcv = context.object.point_cloud_visualizer - return True + filepath = self.filepath + h, t = os.path.split(filepath) + n, e = os.path.splitext(t) + if(e != '.ply'): + self.report({'ERROR'}, "File at '{}' seems not to be a PLY file.".format(filepath)) + return {'CANCELLED'} + + points = [] + try: + points = PlyPointCloudReader(filepath).points + except Exception as e: + self.report({'ERROR'}, str(e)) + return {'CANCELLED'} + if(len(points) == 0): + self.report({'ERROR'}, "No vertices loaded from file at {}".format(filepath)) + return {'CANCELLED'} + + if(not set(('x', 'y', 'z')).issubset(points.dtype.names)): + # this is very unlikely.. + self.report({'ERROR'}, "Loaded data seems to miss vertex locations.") + return False + normals = True + if(not set(('nx', 'ny', 'nz')).issubset(points.dtype.names)): + normals = False + pcv.has_normals = normals + if(not pcv.has_normals): + pcv.illumination = False + vcols = True + if(not set(('red', 'green', 'blue')).issubset(points.dtype.names)): + vcols = False + pcv.has_vcols = vcols + + vs = np.column_stack((points['x'], points['y'], points['z'], )) + + if(normals): + ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) + else: + n = len(points) + ns = np.column_stack((np.full(n, 0.0, dtype=np.float32, ), + np.full(n, 0.0, dtype=np.float32, ), + np.full(n, 1.0, dtype=np.float32, ), )) + + if(vcols): + preferences = bpy.context.preferences + addon_prefs = preferences.addons[__name__].preferences + if(addon_prefs.convert_16bit_colors and points['red'].dtype == 'uint16'): + r8 = (points['red'] / 256).astype('uint8') + g8 = (points['green'] / 256).astype('uint8') + b8 = (points['blue'] / 256).astype('uint8') + if(addon_prefs.gamma_correct_16bit_colors): + cs = np.column_stack(((r8 / 255) ** (1 / 2.2), + (g8 / 255) ** (1 / 2.2), + (b8 / 255) ** (1 / 2.2), + np.ones(len(points), dtype=float, ), )) + else: + cs = np.column_stack((r8 / 255, g8 / 255, b8 / 255, np.ones(len(points), dtype=float, ), )) + cs = cs.astype(np.float32) + else: + # 'uint8' + cs = np.column_stack((points['red'] / 255, points['green'] / 255, points['blue'] / 255, np.ones(len(points), dtype=float, ), )) + cs = cs.astype(np.float32) + else: + n = len(points) + preferences = bpy.context.preferences + addon_prefs = preferences.addons[__name__].preferences + col = addon_prefs.default_vertex_color[:] + col = tuple([c ** (1 / 2.2) for c in col]) + (1.0, ) + cs = np.column_stack((np.full(n, col[0], dtype=np.float32, ), + np.full(n, col[1], dtype=np.float32, ), + np.full(n, col[2], dtype=np.float32, ), + np.ones(n, dtype=np.float32, ), )) + + # append + c = PCVManager.cache[pcv.uuid] + ovs = c['vertices'] + ons = c['normals'] + ocs = c['colors'] + vs = np.concatenate((ovs, vs, )) + ns = np.concatenate((ons, ns, )) + cs = np.concatenate((ocs, cs, )) + + preferences = bpy.context.preferences + addon_prefs = preferences.addons[__name__].preferences + if(addon_prefs.shuffle_points): + l = len(vs) + dt = [('x', '= 0): + return True + return False + + # if(debug_mode()): + # import cProfile + # import pstats + # import io + # pr = cProfile.Profile() + # pr.enable() + + indexes = [] + prgs = Progress(len(vs), indent=1, prefix="> ") + for i, v in enumerate(vs): + prgs.step() + vv = Vector(v) + ''' + inside1 = is_point_inside_mesh_v1(vv, target, ) + inside2 = is_point_inside_mesh_v2(vv, target, ) + inside3 = is_point_inside_mesh_v3(vv, target, ) + # intersect + if(not inside1 and not inside2 and not inside3): + indexes.append(i) + # # exclude + # if(inside1 and inside2 and inside3): + # indexes.append(i) + ''' + + in_bb = is_in_bound_box(v) + if(not in_bb): + # is not in bounds i can skip completely + indexes.append(i) + continue + + inside3 = is_point_inside_mesh_v3(vv, target, ) + if(not inside3): + indexes.append(i) + + # if(debug_mode()): + # pr.disable() + # s = io.StringIO() + # sortby = 'cumulative' + # ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + # ps.print_stats() + # print(s.getvalue()) + + c = PCVManager.cache[pcv.uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] + vs = np.delete(vs, indexes, axis=0, ) + ns = np.delete(ns, indexes, axis=0, ) + cs = np.delete(cs, indexes, axis=0, ) + + log("removed: {} points".format(len(indexes)), 1) + + # cleanup + collection.objects.unlink(target) + bpy.data.objects.remove(target) + bpy.data.meshes.remove(target_mesh) + o.to_mesh_clear() - pcv.color_adjustment_shader_exposure = 0.0 - pcv.color_adjustment_shader_gamma = 1.0 - pcv.color_adjustment_shader_brightness = 0.0 - pcv.color_adjustment_shader_contrast = 1.0 - pcv.color_adjustment_shader_hue = 0.0 - pcv.color_adjustment_shader_saturation = 0.0 - pcv.color_adjustment_shader_value = 0.0 - pcv.color_adjustment_shader_invert = False + # put to cache.. + pcv = context.object.point_cloud_visualizer + PCVManager.update(pcv.uuid, vs, ns, cs, ) - for area in bpy.context.screen.areas: - if(area.type == 'VIEW_3D'): - area.tag_redraw() + _d = datetime.timedelta(seconds=time.time() - _t) + log("completed in {}.".format(_d), 1) return {'FINISHED'} -class PCV_OT_color_adjustment_shader_apply(Operator): - bl_idname = "point_cloud_visualizer.color_adjustment_shader_apply" - bl_label = "Apply" - bl_description = "Apply color adjustments to points, reset and exit" +class PCV_OT_filter_boolean_exclude(Operator): + bl_idname = "point_cloud_visualizer.filter_boolean_exclude" + bl_label = "Exclude" + bl_description = "" @classmethod def poll(cls, context): @@ -9090,145 +7680,200 @@ def poll(cls, context): if(v['uuid'] == pcv.uuid): if(v['ready']): if(v['draw']): - if(pcv.color_adjustment_shader_enabled): - ok = True + o = pcv.filter_boolean_object + if(o is not None): + if(o.type in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): + ok = True return ok def execute(self, context): + log("Exclude:", 0) + _t = time.time() + pcv = context.object.point_cloud_visualizer + o = pcv.filter_boolean_object + if(o is None): + raise Exception() + + def apply_matrix(vs, ns, matrix, ): + matrot = matrix.decompose()[1].to_matrix().to_4x4() + dtv = vs.dtype + dtn = ns.dtype + rvs = np.zeros(vs.shape, dtv) + rns = np.zeros(ns.shape, dtn) + for i in range(len(vs)): + co = matrix @ Vector(vs[i]) + no = matrot @ Vector(ns[i]) + rvs[i] = np.array(co.to_tuple(), dtv) + rns[i] = np.array(no.to_tuple(), dtn) + return rvs, rns c = PCVManager.cache[pcv.uuid] vs = c['vertices'] ns = c['normals'] cs = c['colors'] - cs = cs * (2 ** pcv.color_adjustment_shader_exposure) - cs = np.clip(cs, 0.0, 1.0, ) - cs = cs ** (1 / pcv.color_adjustment_shader_gamma) - cs = np.clip(cs, 0.0, 1.0, ) - cs = (cs - 0.5) * pcv.color_adjustment_shader_contrast + 0.5 + pcv.color_adjustment_shader_brightness - cs = np.clip(cs, 0.0, 1.0, ) - - h = pcv.color_adjustment_shader_hue - s = pcv.color_adjustment_shader_saturation - v = pcv.color_adjustment_shader_value - if(h > 1.0): - h = h % 1.0 - for _i, ca in enumerate(cs): - col = Color(ca[:3]) - _h, _s, _v = col.hsv - _h = (_h + h) % 1.0 - _s += s - _v += v - col.hsv = (_h, _s, _v) - cs[_i][0] = col.r - cs[_i][1] = col.g - cs[_i][2] = col.b - cs = np.clip(cs, 0.0, 1.0, ) - - if(pcv.color_adjustment_shader_invert): - cs = 1.0 - cs - cs = np.clip(cs, 0.0, 1.0, ) - - bpy.ops.point_cloud_visualizer.color_adjustment_shader_reset() - pcv.color_adjustment_shader_enabled = False + # apply parent matrix to points + m = c['object'].matrix_world.copy() + # vs, ns = apply_matrix(vs, ns, m) - if('extra' in c.keys()): - del c['extra'] + sc = context.scene + depsgraph = bpy.context.evaluated_depsgraph_get() + # make target + tmp_mesh = o.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph, ) + target_mesh = tmp_mesh.copy() + target_mesh.name = 'target_mesh_{}'.format(pcv.uuid) + target_mesh.transform(o.matrix_world.copy()) + target_mesh.transform(m.inverted()) + view_layer = context.view_layer + collection = view_layer.active_layer_collection.collection + target = bpy.data.objects.new('target_mesh_{}'.format(pcv.uuid), target_mesh) + collection.objects.link(target) + # still no idea, have to read about it.. + depsgraph.update() + depsgraph = bpy.context.evaluated_depsgraph_get() - PCVManager.update(pcv.uuid, vs, ns, cs, ) + # use object bounding box for fast check if point can even be inside/outside of mesh and then use ray casting etc.. + bounds = [bv[:] for bv in target.bound_box] + xmin = min([v[0] for v in bounds]) + xmax = max([v[0] for v in bounds]) + ymin = min([v[1] for v in bounds]) + ymax = max([v[1] for v in bounds]) + zmin = min([v[2] for v in bounds]) + zmax = max([v[2] for v in bounds]) - return {'FINISHED'} - - -class PCV_OT_clip_planes_from_bbox(Operator): - bl_idname = "point_cloud_visualizer.clip_planes_from_bbox" - bl_label = "Set Clip Planes From Object Bounding Box" - bl_description = "Set clip planes from object bounding box" - - @classmethod - def poll(cls, context): - if(context.object is None): + def is_in_bound_box(v): + x = False + if(xmin < v[0] < xmax): + x = True + y = False + if(ymin < v[1] < ymax): + y = True + z = False + if(zmin < v[2] < zmax): + z = True + if(x and y and z): + return True return False - pcv = context.object.point_cloud_visualizer - ok = False + # v1 raycasting in three axes and counting hits + def is_point_inside_mesh_v1(p, o, ): + axes = [Vector((1.0, 0.0, 0.0)), Vector((0.0, 1.0, 0.0)), Vector((0.0, 0.0, 1.0)), ] + r = False + for a in axes: + v = p + c = 0 + while True: + _, l, n, i = o.ray_cast(v, v + a * 10000.0) + if(i == -1): + break + c += 1 + v = l + a * 0.00001 + if(c % 2 == 0): + r = True + break + return not r - bbo = pcv.clip_planes_from_bbox_object - if(bbo is not None): + # v2 raycasting and counting hits + def is_point_inside_mesh_v2(p, o, shift=0.000001, search_distance=10000.0, ): + p = Vector(p) + path = [] + hits = 0 + direction = Vector((0.0, 0.0, 1.0)) + opposite_direction = direction.copy() + opposite_direction.negate() + path.append(p) + loc = p ok = True + + def shift_vector(co, no, v): + return co + (no.normalized() * v) + + while(ok): + end = shift_vector(loc, direction, search_distance) + loc = shift_vector(loc, direction, shift) + _, loc, nor, ind = o.ray_cast(loc, end) + if(ind != -1): + a = shift_vector(loc, opposite_direction, shift) + path.append(a) + hits += 1 + else: + ok = False + if(hits % 2 == 1): + return True + return False - return ok - - def execute(self, context): - pcv = context.object.point_cloud_visualizer - bbo = pcv.clip_planes_from_bbox_object - - mw = bbo.matrix_world - vs = [mw @ Vector(v) for v in bbo.bound_box] - - # 0 front left down - # 1 front left up - # 2 back left up - # 3 back left down - # 4 front right down - # 5 front right up - # 6 back right up - # 7 back right down - # 2-----------6 - # / /| - # 1-----------5 | - # | | | - # | | | - # | 3 | 7 - # | | / - # 0-----------4 + # v3 closest point on mesh normal checking + def is_point_inside_mesh_v3(p, o, ): + _, loc, nor, ind = o.closest_point_on_mesh(p) + if(ind != -1): + v = loc - p + d = v.dot(nor) + if(d >= 0): + return True + return False - fs = ( - (0, 4, 5, 1), # front - (3, 2, 6, 7), # back - (1, 5, 6, 2), # top - (0, 3, 7, 4), # bottom - (0, 1, 2, 3), # left - (4, 7, 6, 5), # right - ) + indexes = [] + prgs = Progress(len(vs), indent=1, prefix="> ") + for i, v in enumerate(vs): + prgs.step() + vv = Vector(v) + ''' + inside1 = is_point_inside_mesh_v1(vv, target, ) + inside2 = is_point_inside_mesh_v2(vv, target, ) + inside3 = is_point_inside_mesh_v3(vv, target, ) + # # intersect + # if(not inside1 and not inside2 and not inside3): + # indexes.append(i) + # exclude + if(inside1 and inside2 and inside3): + indexes.append(i) + ''' + + in_bb = is_in_bound_box(v) + if(in_bb): + # indexes.append(i) + # continue + + # is in bound so i can check further + inside3 = is_point_inside_mesh_v3(vv, target, ) + if(inside3): + indexes.append(i) + + # inside3 = is_point_inside_mesh_v3(vv, target, ) + # if(inside3): + # indexes.append(i) - quads = [[vs[fs[i][j]] for j in range(4)] for i in range(6)] - ns = [mathutils.geometry.normal(quads[i]) for i in range(6)] - for i in range(6): - # FIXME: if i need to do this, it is highly probable i have something wrong.. somewhere.. - ns[i].negate() + c = PCVManager.cache[pcv.uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] + vs = np.delete(vs, indexes, axis=0, ) + ns = np.delete(ns, indexes, axis=0, ) + cs = np.delete(cs, indexes, axis=0, ) - ds = [] - for i in range(6): - v = quads[i][0] - n = ns[i] - d = mathutils.geometry.distance_point_to_plane(Vector(), v, n) - ds.append(d) + log("removed: {} points".format(len(indexes)), 1) - a = [ns[i].to_tuple() + (ds[i], ) for i in range(6)] - pcv.clip_plane0 = a[0] - pcv.clip_plane1 = a[1] - pcv.clip_plane2 = a[2] - pcv.clip_plane3 = a[3] - pcv.clip_plane4 = a[4] - pcv.clip_plane5 = a[5] + # cleanup + collection.objects.unlink(target) + bpy.data.objects.remove(target) + bpy.data.meshes.remove(target_mesh) + o.to_mesh_clear() - pcv.clip_shader_enabled = True - pcv.clip_plane0_enabled = True - pcv.clip_plane1_enabled = True - pcv.clip_plane2_enabled = True - pcv.clip_plane3_enabled = True - pcv.clip_plane4_enabled = True - pcv.clip_plane5_enabled = True + # put to cache.. + pcv = context.object.point_cloud_visualizer + PCVManager.update(pcv.uuid, vs, ns, cs, ) + + _d = datetime.timedelta(seconds=time.time() - _t) + log("completed in {}.".format(_d), 1) return {'FINISHED'} -class PCV_OT_clip_planes_reset(Operator): - bl_idname = "point_cloud_visualizer.clip_planes_reset" - bl_label = "Reset Clip Planes" - bl_description = "Reset all clip planes" +class PCV_OT_reload(Operator): + bl_idname = "point_cloud_visualizer.reload" + bl_label = "Reload" + bl_description = "Reload points from file" @classmethod def poll(cls, context): @@ -9236,38 +7881,42 @@ def poll(cls, context): return False pcv = context.object.point_cloud_visualizer - ok = True - - return ok + # if(pcv.filepath != '' and pcv.uuid != '' and not pcv.runtime): + if(pcv.filepath != '' and pcv.uuid != ''): + return True + return False def execute(self, context): pcv = context.object.point_cloud_visualizer - pcv.clip_planes_from_bbox_object = None + draw = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + draw = True + bpy.ops.point_cloud_visualizer.erase() - z = (0.0, 0.0, 0.0, 0.0, ) - pcv.clip_plane0 = z - pcv.clip_plane1 = z - pcv.clip_plane2 = z - pcv.clip_plane3 = z - pcv.clip_plane4 = z - pcv.clip_plane5 = z + if(pcv.runtime): + c = PCVManager.cache[pcv.uuid] + points = c['points'] + vs = np.column_stack((points['x'], points['y'], points['z'], )) + ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) + cs = c['colors_original'] + PCVManager.update(pcv.uuid, vs, ns, cs, ) + else: + bpy.ops.point_cloud_visualizer.load_ply_to_cache(filepath=pcv.filepath) - pcv.clip_shader_enabled = False - pcv.clip_plane0_enabled = False - pcv.clip_plane1_enabled = False - pcv.clip_plane2_enabled = False - pcv.clip_plane3_enabled = False - pcv.clip_plane4_enabled = False - pcv.clip_plane5_enabled = False + if(draw): + bpy.ops.point_cloud_visualizer.draw() return {'FINISHED'} -class PCV_OT_clip_planes_from_camera_view(Operator): - bl_idname = "point_cloud_visualizer.clip_planes_from_camera_view" - bl_label = "Set Clip Planes From Camera View" - bl_description = "Set clip planes from active camera view" +class PCV_OT_sequence_preload(Operator): + bl_idname = "point_cloud_visualizer.sequence_preload" + bl_label = "Preload Sequence" + bl_description = "Preload sequence of PLY files. Files should be numbered starting at 1. Missing files in sequence will be skipped." @classmethod def poll(cls, context): @@ -9275,1149 +7924,740 @@ def poll(cls, context): return False pcv = context.object.point_cloud_visualizer + if(pcv.uuid in PCVSequence.cache.keys()): + return False ok = False - - s = bpy.context.scene - o = s.camera - if(o): - ok = True - + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + ok = True return ok def execute(self, context): + log('Preload Sequence..') pcv = context.object.point_cloud_visualizer - o = bpy.context.object + # pcv.sequence_enabled = True - s = bpy.context.scene - c = s.camera - cd = c.data - rx = s.render.resolution_x - ry = s.render.resolution_y - w = 0.5 * cd.sensor_width / cd.lens - if(rx > ry): - x = w - y = w * ry / rx - else: - x = w * rx / ry - y = w + dirpath = os.path.dirname(pcv.filepath) + files = [] + for (p, ds, fs) in os.walk(dirpath): + files.extend(fs) + break - lr = Vector((x, -y, -1.0, )) - ur = Vector((x, y, -1.0, )) - ll = Vector((-x, -y, -1.0, )) - ul = Vector((-x, y, -1.0, )) + fs = [f for f in files if f.lower().endswith('.ply')] + f = os.path.split(pcv.filepath)[1] - z = Vector() - n0 = mathutils.geometry.normal((z, lr, ur)) - n1 = mathutils.geometry.normal((z, ur, ul)) - n2 = mathutils.geometry.normal((z, ul, ll)) - n3 = mathutils.geometry.normal((z, ll, lr)) + pattern = re.compile(r'(\d+)(?!.*(\d+))') - n0.negate() - n1.negate() - n2.negate() - n3.negate() + m = re.search(pattern, f, ) + if(m is not None): + prefix = f[:m.start()] + suffix = f[m.end():] + else: + self.report({'ERROR'}, 'Filename does not contain any sequence number') + return {'CANCELLED'} - m = c.matrix_world - l, r, _ = m.decompose() - rm = r.to_matrix().to_4x4() - lrm = Matrix.Translation(l).to_4x4() @ rm + sel = [] - omi = o.matrix_world.inverted() - _, r, _ = omi.decompose() - orm = r.to_matrix().to_4x4() + for n in fs: + m = re.search(pattern, n, ) + if(m is not None): + # some numbers present, lets compare with selected file prefix/suffix + pre = n[:m.start()] + if(pre == prefix): + # prefixes match + suf = n[m.end():] + if(suf == suffix): + # suffixes match, extract number + si = n[m.start():m.end()] + try: + # try convert it to integer + i = int(si) + # and store as selected file + sel.append((i, n)) + except ValueError: + pass - n0 = orm @ rm @ n0 - n1 = orm @ rm @ n1 - n2 = orm @ rm @ n2 - n3 = orm @ rm @ n3 + # sort by sequence number + sel.sort() + # fabricate list with missing sequence numbers as None + sequence = [[None] for i in range(sel[-1][0])] + for i, n in sel: + sequence[i - 1] = (i, n) + for i in range(len(sequence)): + if(sequence[i][0] is None): + sequence[i] = [i, None] - v0 = omi @ lrm @ lr - v1 = omi @ lrm @ ur - v2 = omi @ lrm @ ul - v3 = omi @ lrm @ ll + log('found files:', 1) + for i, n in sequence: + log('{}: {}'.format(i, n), 2) - d0 = mathutils.geometry.distance_point_to_plane(Vector(), v0, n0) - d1 = mathutils.geometry.distance_point_to_plane(Vector(), v1, n1) - d2 = mathutils.geometry.distance_point_to_plane(Vector(), v2, n2) - d3 = mathutils.geometry.distance_point_to_plane(Vector(), v3, n3) + log('preloading..', 1) + # this is our sequence with matching filenames, sorted by numbers with missing as None, now load it all.. + cache = [] + for i, n in sequence: + if(n is not None): + p = os.path.join(dirpath, n) + points = [] + try: + points = PlyPointCloudReader(p).points + except Exception as e: + self.report({'ERROR'}, str(e)) + if(len(points) == 0): + self.report({'ERROR'}, "No vertices loaded from file at {}".format(p)) + else: + if(not set(('x', 'y', 'z')).issubset(points.dtype.names)): + self.report({'ERROR'}, "Loaded data seems to miss vertex locations.") + return {'CANCELLED'} + + vs = np.column_stack((points['x'], points['y'], points['z'], )) + vs = vs.astype(np.float32) + + if(not set(('nx', 'ny', 'nz')).issubset(points.dtype.names)): + ns = None + else: + ns = np.column_stack((points['nx'], points['ny'], points['nz'], )) + ns = ns.astype(np.float32) + if(not set(('red', 'green', 'blue')).issubset(points.dtype.names)): + cs = None + else: + cs = np.column_stack((points['red'] / 255, points['green'] / 255, points['blue'] / 255, np.ones(len(points), dtype=float, ), )) + cs = cs.astype(np.float32) + + cache.append({'index': i, + 'name': n, + 'path': p, + 'vs': vs, + 'ns': ns, + 'cs': cs, + 'points': points, }) - # TODO: add plane behind camera (not much needed anyway, but for consistency), but more important, add plane in clipping distance set on camera - # TODO: ORTHO camera does not work + log('...', 1) + log('loaded {} item(s)'.format(len(cache)), 1) + log('initializing..', 1) - pcv.clip_plane0 = n0.to_tuple() + (d0, ) - pcv.clip_plane1 = n1.to_tuple() + (d1, ) - pcv.clip_plane2 = n2.to_tuple() + (d2, ) - pcv.clip_plane3 = n3.to_tuple() + (d3, ) - pcv.clip_plane4 = (0.0, 0.0, 0.0, 0.0, ) - pcv.clip_plane5 = (0.0, 0.0, 0.0, 0.0, ) + PCVSequence.init() - pcv.clip_shader_enabled = True - pcv.clip_plane0_enabled = True - pcv.clip_plane1_enabled = True - pcv.clip_plane2_enabled = True - pcv.clip_plane3_enabled = True - pcv.clip_plane4_enabled = False - pcv.clip_plane5_enabled = False + ci = {'data': cache, + 'uuid': pcv.uuid, + 'pcv': pcv, } + PCVSequence.cache[pcv.uuid] = ci + + log('force frame update..', 1) + sc = bpy.context.scene + cf = sc.frame_current + sc.frame_current = cf + log('done.', 1) return {'FINISHED'} -class PCVIV2_OT_init(Operator): - bl_idname = "point_cloud_visualizer.pcviv_init" - bl_label = "Initialize" - bl_description = "Initialize Instance Visualizer" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - return True - - def execute(self, context): - PCVIV2Manager.init() - return {'FINISHED'} - - -class PCVIV2_OT_deinit(Operator): - bl_idname = "point_cloud_visualizer.pcviv_deinit" - bl_label = "Deinitialize" - bl_description = "Deinitialize Instance Visualizer" +class PCV_OT_sequence_clear(Operator): + bl_idname = "point_cloud_visualizer.sequence_clear" + bl_label = "Clear Sequence" + bl_description = "Clear preloaded sequence cache and reset all" @classmethod def poll(cls, context): if(context.object is None): return False - return True + + pcv = context.object.point_cloud_visualizer + if(pcv.uuid in PCVSequence.cache.keys()): + return True + return False + # ok = False + # for k, v in PCVManager.cache.items(): + # if(v['uuid'] == pcv.uuid): + # if(v['ready']): + # if(v['draw']): + # ok = True + # return ok def execute(self, context): - PCVIV2Manager.deinit() + pcv = context.object.point_cloud_visualizer + + del PCVSequence.cache[pcv.uuid] + if(len(PCVSequence.cache.items()) == 0): + PCVSequence.deinit() + + # c = PCVManager.cache[pcv.uuid] + # vs = c['vertices'] + # ns = c['normals'] + # cs = c['colors'] + # PCVManager.update(pcv.uuid, vs, ns, cs, ) + + bpy.ops.point_cloud_visualizer.reload() + return {'FINISHED'} -class PCVIV2_OT_reset(Operator): - bl_idname = "point_cloud_visualizer.pcviv_reset" - bl_label = "Reset" - bl_description = "Reset active object particle systems visualizations" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - return True +class PCV_OT_seq_init(Operator): + bl_idname = "point_cloud_visualizer.seq_init" + bl_label = "seq_init" def execute(self, context): - PCVIV2Manager.reset(context.object, ) + PCVSequence.init() + context.area.tag_redraw() return {'FINISHED'} -class PCVIV2_OT_reset_all(Operator): - bl_idname = "point_cloud_visualizer.pcviv_reset_all" - bl_label = "Reset All" - bl_description = "Reset all particle systems visualizations on all objects" - - @classmethod - def poll(cls, context): - if(context.object is None): - return False - return True +class PCV_OT_seq_deinit(Operator): + bl_idname = "point_cloud_visualizer.seq_deinit" + bl_label = "seq_deinit" def execute(self, context): - PCVIV2Manager.reset_all() + PCVSequence.deinit() + context.area.tag_redraw() return {'FINISHED'} -class PCVIV2_OT_update(Operator): - bl_idname = "point_cloud_visualizer.pcviv_update" - bl_label = "Update" - bl_description = "Update point cloud visualization by particle system UUID" - - uuid: StringProperty(name="UUID", default='', ) +class PCV_OT_generate_point_cloud(Operator): + bl_idname = "point_cloud_visualizer.generate_from_mesh" + bl_label = "Generate" + bl_description = "Generate colored point cloud from mesh (or object convertible to mesh)" @classmethod def poll(cls, context): if(context.object is None): return False + + # pcv = context.object.point_cloud_visualizer + # if(pcv.uuid in PCVSequence.cache.keys()): + # return False + # ok = False + # for k, v in PCVManager.cache.items(): + # if(v['uuid'] == pcv.uuid): + # if(v['ready']): + # if(v['draw']): + # ok = True + # return ok + return True def execute(self, context): - PCVIV2Manager.update(context.object, self.uuid, ) + log("Generate From Mesh:", 0) + _t = time.time() + + o = context.object + + if(o.type not in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): + self.report({'ERROR'}, "Object does not have geometry data.") + return {'CANCELLED'} + + pcv = o.point_cloud_visualizer + + if(pcv.generate_source not in ('SURFACE', 'VERTICES', 'PARTICLES', )): + self.report({'ERROR'}, "Source not implemented.") + return {'CANCELLED'} + + n = pcv.generate_number_of_points + r = random.Random(pcv.generate_seed) + + if(pcv.generate_colors in ('CONSTANT', 'UVTEX', 'VCOLS', 'GROUP_MONO', 'GROUP_COLOR', )): + if(o.type in ('CURVE', 'SURFACE', 'FONT', ) and pcv.generate_colors != 'CONSTANT'): + self.report({'ERROR'}, "Object type does not support UV textures, vertex colors or vertex groups.") + return {'CANCELLED'} + else: + self.report({'ERROR'}, "Color generation not implemented.") + return {'CANCELLED'} + + if(o.type != 'MESH'): + vcols = None + uvtex = None + vgroup = None + else: + # all of following should return None is not available, at least for mesh object + vcols = o.data.vertex_colors.active + uvtex = o.data.uv_layers.active + vgroup = o.vertex_groups.active + + if(pcv.generate_source == 'VERTICES'): + try: + sampler = PCVVertexSampler(context, o, + colorize=pcv.generate_colors, + constant_color=pcv.generate_constant_color, + vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) + except Exception as e: + self.report({'ERROR'}, str(e), ) + return {'CANCELLED'} + elif(pcv.generate_source == 'SURFACE'): + if(pcv.generate_algorithm == 'WEIGHTED_RANDOM_IN_TRIANGLE'): + try: + sampler = PCVTriangleSurfaceSampler(context, o, n, r, + colorize=pcv.generate_colors, + constant_color=pcv.generate_constant_color, + vcols=vcols, uvtex=uvtex, vgroup=vgroup, + exact_number_of_points=pcv.generate_exact_number_of_points, ) + except Exception as e: + self.report({'ERROR'}, str(e), ) + return {'CANCELLED'} + elif(pcv.generate_algorithm == 'POISSON_DISK_SAMPLING'): + try: + sampler = PCVPoissonDiskSurfaceSampler(context, o, r, minimal_distance=pcv.generate_minimal_distance, + sampling_exponent=pcv.generate_sampling_exponent, + colorize=pcv.generate_colors, + constant_color=pcv.generate_constant_color, + vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) + except Exception as e: + self.report({'ERROR'}, str(e), ) + return {'CANCELLED'} + else: + self.report({'ERROR'}, "Algorithm not implemented.") + return {'CANCELLED'} + + elif(pcv.generate_source == 'PARTICLES'): + try: + alive_only = True + if(pcv.generate_source_psys == 'ALL'): + alive_only = False + sampler = PCVParticleSystemSampler(context, o, alive_only=alive_only, + colorize=pcv.generate_colors, + constant_color=pcv.generate_constant_color, + vcols=vcols, uvtex=uvtex, vgroup=vgroup, ) + except Exception as e: + self.report({'ERROR'}, str(e), ) + return {'CANCELLED'} + else: + self.report({'ERROR'}, "Source type not implemented.") + return {'CANCELLED'} + + vs = sampler.vs + ns = sampler.ns + cs = sampler.cs + + log("generated {} points.".format(len(vs)), 1) + + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + ok = True + if(ok): + bpy.ops.point_cloud_visualizer.erase() + + c = PCVControl(o) + c.draw(vs, ns, cs) + + if(debug_mode()): + o.display_type = 'BOUNDS' + + _d = datetime.timedelta(seconds=time.time() - _t) + log("completed in {}.".format(_d), 1) + return {'FINISHED'} -class PCVIV2_OT_update_all(Operator): - bl_idname = "point_cloud_visualizer.pcviv_update_all" - bl_label = "Update All" - bl_description = "Update all point cloud visualizations for active object" +class PCV_OT_reset_runtime(Operator): + bl_idname = "point_cloud_visualizer.reset_runtime" + bl_label = "Reset Runtime" + bl_description = "Reset PCV to its default state if in runtime mode (displayed data is set with python and not with ui)" @classmethod def poll(cls, context): if(context.object is None): return False - return True + pcv = context.object.point_cloud_visualizer + if(pcv.runtime): + return True + return False def execute(self, context): - PCVIV2Manager.update_all(context.object, ) + o = context.object + c = PCVControl(o) + c.erase() + c.reset() return {'FINISHED'} -class PCVIV2_OT_dev_transform_normals(Operator): - bl_idname = "point_cloud_visualizer.pcviv_dev_transform_normals" - bl_label = "dev_transform_normals" - bl_description = "" +class PCV_OT_generate_volume_point_cloud(Operator): + bl_idname = "point_cloud_visualizer.generate_volume_from_mesh" + bl_label = "Generate Volume" + bl_description = "Generate colored point cloud in mesh (or object convertible to mesh) volume" @classmethod def poll(cls, context): if(context.object is None): return False + + # pcv = context.object.point_cloud_visualizer + # if(pcv.uuid in PCVSequence.cache.keys()): + # return False + # ok = False + # for k, v in PCVManager.cache.items(): + # if(v['uuid'] == pcv.uuid): + # if(v['ready']): + # if(v['draw']): + # ok = True + # return ok + return True def execute(self, context): + log("Generate From Mesh:", 0) + _t = time.time() + o = context.object pcv = o.point_cloud_visualizer + n = pcv.generate_number_of_points + r = random.Random(pcv.generate_seed) + g = PCVRandomVolumeSampler(o, n, r, ) + vs = g.vs + ns = g.ns + cs = g.cs - # sample target object - to = pcv.dev_transform_normals_target_object - sampler = PCVIVDraftWeightedFixedCountNumpySampler(bpy.context, to, count=1000, colorize='CONSTANT', constant_color=(1.0, 0.0, 0.0), ) - vs = sampler.vs - ns = sampler.ns - cs = sampler.cs - - # target and emitter matrices - m = to.matrix_world - m = o.matrix_world.inverted() @ m - - # transform vertices - vs.shape = (-1, 3) - vs = np.c_[vs, np.ones(vs.shape[0])] - vs = np.dot(m, vs.T)[0:3].T.reshape((-1)) - vs.shape = (-1, 3) - - # transform normals - _, r, _ = m.decompose() - m = r.to_matrix().to_4x4() + log("generated {} points.".format(len(vs)), 1) - ns.shape = (-1, 3) - ns = np.c_[ns, np.ones(ns.shape[0])] - ns = np.dot(m, ns.T)[0:3].T.reshape((-1)) - ns.shape = (-1, 3) + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + ok = True + if(ok): + bpy.ops.point_cloud_visualizer.erase() - # for n in ns: - # print(sum(n ** 2) ** 0.5) + c = PCVControl(o) + c.draw(vs, ns, cs) - # fixed sizes - sz = np.full(len(vs), 3, dtype=np.int, ) + if(debug_mode()): + o.display_type = 'BOUNDS' - # draw - c = PCVIV2Control(o) - c.draw(vs, ns, cs, sz, ) + _d = datetime.timedelta(seconds=time.time() - _t) + log("completed in {}.".format(_d), 1) return {'FINISHED'} -class PCVIV2Manager(): - initialized = False - cache = {} - - use_extra_handlers = True - undo_redo_catalogue = {} - psys_existence = {} - - pre_save_state = {} - - @classmethod - def init(cls): - if(cls.initialized): - return - log("init", prefix='>>>', ) - # bpy.app.handlers.depsgraph_update_post.append(cls.handler) - bpy.app.handlers.depsgraph_update_pre.append(cls.uuid_handler) - - if(cls.use_extra_handlers): - # undo/redo handling - bpy.app.handlers.redo_pre.append(cls.redo_pre) - bpy.app.handlers.redo_post.append(cls.redo_post) - bpy.app.handlers.undo_pre.append(cls.undo_pre) - bpy.app.handlers.undo_post.append(cls.undo_post) - # psys removal handling - bpy.app.handlers.depsgraph_update_post.append(cls.psys_existence_post) - - bpy.app.handlers.save_pre.append(cls.save_handler_pre) - bpy.app.handlers.save_post.append(cls.save_handler_post) - - cls.initialized = True - cls.uuid_handler(None) - - @classmethod - def deinit(cls): - if(not cls.initialized): - return - log("deinit", prefix='>>>', ) - # bpy.app.handlers.depsgraph_update_post.remove(cls.handler) - bpy.app.handlers.depsgraph_update_pre.remove(cls.uuid_handler) - - if(cls.use_extra_handlers): - # undo/redo handling - bpy.app.handlers.redo_pre.remove(cls.redo_pre) - bpy.app.handlers.redo_post.remove(cls.redo_post) - bpy.app.handlers.undo_pre.remove(cls.undo_pre) - bpy.app.handlers.undo_post.remove(cls.undo_post) - # psys removal handling - bpy.app.handlers.depsgraph_update_post.remove(cls.psys_existence_post) - - cls.initialized = False - - @classmethod - def uuid_handler(cls, scene, ): - if(not cls.initialized): - return - # log("uuid_handler", prefix='>>>', ) - dps = bpy.data.particles - for ps in dps: - pcviv = ps.pcv_instance_visualizer - if(pcviv.uuid == ""): - log("uuid_handler: found psys without uuid", 1, prefix='>>>', ) - pcviv.uuid = str(uuid.uuid1()) - # if psys is added outside of 3dview - cls._redraw_view_3d() +class PCV_OT_color_adjustment_shader_reset(Operator): + bl_idname = "point_cloud_visualizer.color_adjustment_shader_reset" + bl_label = "Reset" + bl_description = "Reset color adjustment values" @classmethod - def psys_existence_post(cls, scene, ): - log("existence post", prefix='>>>', ) - - # NOTE: this is run on every update that happen in blender, even on something completely unrelated to pcv, pcviv or particles, this might slow down everything - - ls = [] - for pset in bpy.data.particles: - pcviv = pset.pcv_instance_visualizer - if(pcviv.uuid != ""): - ls.append((pcviv.uuid, pset, )) + def poll(cls, context): + if(context.object is None): + return False - for u, pset in ls: - if(u in cls.cache.keys()): - # was in cache, so it should draw, unless psys was removed, or its object - # so check for objects with psys with that pset - ok = False - for o in bpy.data.objects: - for psys in o.particle_systems: - if(psys.settings == pset): + pcv = context.object.point_cloud_visualizer + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + if(pcv.color_adjustment_shader_enabled): ok = True - if(not ok): - del cls.cache[u] - # now i should run update on object, but how to find which one was it? - if(u in cls.psys_existence.keys()): - onm = cls.psys_existence[u] - o = bpy.data.objects.get(onm) - if(o is not None): - # object still exist - cls.update_all(o) - - cls.psys_existence = {} - for o in bpy.data.objects: - for i, psys in enumerate(o.particle_systems): - u = psys.settings.pcv_instance_visualizer.uuid - if(u in cls.cache.keys()): - cls.psys_existence[u] = o.name - - @classmethod - def save_handler_pre(cls, scene, ): - # store by object name which is used as instance visualizer - for o in bpy.data.objects: - pcv = o.point_cloud_visualizer - if(pcv.instance_visualizer_active_hidden_value): - cls.pre_save_state[o.name] = True - pcv.instance_visualizer_active_hidden_value = False - - @classmethod - def save_handler_post(cls, scene, ): - # revert pre save changes.. - for n, v in cls.pre_save_state.items(): - o = bpy.data.objects.get(n) - if(o is not None): - pcv = o.point_cloud_visualizer - pcv.instance_visualizer_active_hidden_value = v - - @classmethod - def undo_pre(cls, scene, ): - log("undo/redo pre", prefix='>>>', ) - - for o in bpy.data.objects: - pcv = o.point_cloud_visualizer - for i, psys in enumerate(o.particle_systems): - pset = psys.settings - pcviv = pset.pcv_instance_visualizer - u = pcviv.uuid - if(u in cls.cache.keys()): - if(o.name not in cls.undo_redo_catalogue.keys()): - cls.undo_redo_catalogue[o.name] = {} - cls.undo_redo_catalogue[o.name][pset.name] = u - - @classmethod - def undo_post(cls, scene, ): - log("undo/redo post", prefix='>>>', ) - - for onm, psnms in cls.undo_redo_catalogue.items(): - o = bpy.data.objects.get(onm) - if(o is not None): - dirty = False - # ok, object still exists, unless it was object rename that was undone.. huh? now what? - for psnm, u in psnms.items(): - pset = bpy.data.particles.get(psnm) - if(pset is not None): - found = False - for psys in o.particle_systems: - if(psys.settings == pset): - found = True - break - if(found): - # all is ok - pass - else: - # psys is gone, we should remove it - dirty = True - else: - # pset is there, but psys is gone, we should remove it - dirty = True - - if(dirty): - cls.update_all(o) - else: - # object is gone, we should remove it - # this is handled by PCV, no object - no draw - pass - - cls.undo_redo_catalogue = {} - - @classmethod - def redo_pre(cls, scene, ): - log("undo/redo pre", prefix='>>>', ) - cls.undo_pre(scene) - - @classmethod - def redo_post(cls, scene, ): - log("undo/redo post", prefix='>>>', ) - cls.undo_post(scene) + return ok - @classmethod - def reset_all(cls): - # if(not cls.initialized): - # return + def execute(self, context): + pcv = context.object.point_cloud_visualizer - log("reset_all", prefix='>>>', ) - cls.deinit() + pcv.color_adjustment_shader_exposure = 0.0 + pcv.color_adjustment_shader_gamma = 1.0 + pcv.color_adjustment_shader_brightness = 0.0 + pcv.color_adjustment_shader_contrast = 1.0 + pcv.color_adjustment_shader_hue = 0.0 + pcv.color_adjustment_shader_saturation = 0.0 + pcv.color_adjustment_shader_value = 0.0 + pcv.color_adjustment_shader_invert = False - for o in bpy.data.objects: - pcv = o.point_cloud_visualizer - pcv.instance_visualizer_active_hidden_value = False - pcv.dev_minimal_shader_variable_size_enabled = False - pcv.pcviv_debug_draw = '' - for i, psys in enumerate(o.particle_systems): - pset = psys.settings - pcviv = pset.pcv_instance_visualizer - pcviv.uuid = "" - pcviv.draw = True - pcviv.debug_update = "" - - # NOTE: maybe store these somewhere, but these are defaults, so makes sense too - # NOTE: switch to BOUNDS to keep viewport alive - if(pset.render_type == 'COLLECTION'): - for co in pset.instance_collection.objects: - # co.display_type = 'TEXTURED' - co.display_type = 'BOUNDS' - elif(pset.render_type == 'OBJECT'): - # pset.instance_object.display_type = 'TEXTURED' - pset.instance_object.display_type = 'BOUNDS' - pset.display_method = 'RENDER' - - c = PCVIV2Control(o) - c.reset() + for area in bpy.context.screen.areas: + if(area.type == 'VIEW_3D'): + area.tag_redraw() - cls.cache = {} + return {'FINISHED'} + + +class PCV_OT_color_adjustment_shader_apply(Operator): + bl_idname = "point_cloud_visualizer.color_adjustment_shader_apply" + bl_label = "Apply" + bl_description = "Apply color adjustments to points, reset and exit" @classmethod - def reset(cls, o, ): - # if(not cls.initialized): - # return - - log("reset", prefix='>>>', ) - cls.deinit() - - pcv = o.point_cloud_visualizer - pcv.instance_visualizer_active_hidden_value = False - pcv.dev_minimal_shader_variable_size_enabled = False - pcv.pcviv_debug_draw = '' - keys = [] - for i, psys in enumerate(o.particle_systems): - pset = psys.settings - pcviv = pset.pcv_instance_visualizer - keys.append(pcviv.uuid) - pcviv.uuid = "" - pcviv.draw = True - pcviv.debug_update = "" - - # NOTE: maybe store these somewhere, but these are defaults, so makes sense too - # NOTE: switch to BOUNDS to keep viewport alive - if(pset.render_type == 'COLLECTION'): - for co in pset.instance_collection.objects: - # co.display_type = 'TEXTURED' - co.display_type = 'BOUNDS' - elif(pset.render_type == 'OBJECT'): - # pset.instance_object.display_type = 'TEXTURED' - pset.instance_object.display_type = 'BOUNDS' - pset.display_method = 'RENDER' - - c = PCVIV2Control(o) - c.reset() - - # delete just reseted keys from cache - for k in keys: - if(k in cls.cache): - del cls.cache[k] - # # initialize back if there are some keys in cache to keep other objects with visualizations - # if(len(cls.cache.keys()) > 0): - # cls.init() + def poll(cls, context): + if(context.object is None): + return False - # always initialize back after single object reset - cls.init() + pcv = context.object.point_cloud_visualizer + ok = False + for k, v in PCVManager.cache.items(): + if(v['uuid'] == pcv.uuid): + if(v['ready']): + if(v['draw']): + if(pcv.color_adjustment_shader_enabled): + ok = True + return ok - @classmethod - def update(cls, o, uuid, skip_render=False, ): - if(not cls.initialized): - return - - pcv = o.point_cloud_visualizer - pcv.instance_visualizer_active_hidden_value = True - - log("update", prefix='>>>', ) + def execute(self, context): + pcv = context.object.point_cloud_visualizer - if(uuid in cls.cache.keys()): - log("update: is cached, setting dirty..", 1, prefix='>>>', ) - cls.cache[uuid]['dirty'] = True - else: - found = False - for i, psys in enumerate(o.particle_systems): - pcviv = psys.settings.pcv_instance_visualizer - if(pcviv.uuid == uuid): - found = True - break - if(not found): - raise Exception('PCVIV2Manager.update, uuid {} not found in particle systems on object: {}'.format(uuid, o.name)) - - log("update: psys not in cache, adding..", 1, prefix='>>>', ) - # not yet in cache, we should fix that - ci = {'uuid': uuid, - # we just started, so it's always dirty, yeah baby.. - 'dirty': True, - 'draw': pcviv.draw, - # 'draw': True, - 'vs': None, - 'ns': None, - 'cs': None, - 'sz': None, } - cls.cache[uuid] = ci - - if(not skip_render): - cls.render(o) - - @classmethod - def update_all(cls, o, ): - if(not cls.initialized): - return + c = PCVManager.cache[pcv.uuid] + vs = c['vertices'] + ns = c['normals'] + cs = c['colors'] - log("update_all", prefix='>>>', ) + cs = cs * (2 ** pcv.color_adjustment_shader_exposure) + cs = np.clip(cs, 0.0, 1.0, ) + cs = cs ** (1 / pcv.color_adjustment_shader_gamma) + cs = np.clip(cs, 0.0, 1.0, ) + cs = (cs - 0.5) * pcv.color_adjustment_shader_contrast + 0.5 + pcv.color_adjustment_shader_brightness + cs = np.clip(cs, 0.0, 1.0, ) - ls = [] - for i, psys in enumerate(o.particle_systems): - pcviv = psys.settings.pcv_instance_visualizer - ls.append(pcviv.uuid) - for u in ls: - cls.update(o, u, True, ) + h = pcv.color_adjustment_shader_hue + s = pcv.color_adjustment_shader_saturation + v = pcv.color_adjustment_shader_value + if(h > 1.0): + h = h % 1.0 + for _i, ca in enumerate(cs): + col = Color(ca[:3]) + _h, _s, _v = col.hsv + _h = (_h + h) % 1.0 + _s += s + _v += v + col.hsv = (_h, _s, _v) + cs[_i][0] = col.r + cs[_i][1] = col.g + cs[_i][2] = col.b + cs = np.clip(cs, 0.0, 1.0, ) - cls.render(o) - - @classmethod - def draw_update(cls, o, uuid, do_draw, ): - if(not cls.initialized): - return + if(pcv.color_adjustment_shader_invert): + cs = 1.0 - cs + cs = np.clip(cs, 0.0, 1.0, ) - log("draw_update", prefix='>>>', ) + bpy.ops.point_cloud_visualizer.color_adjustment_shader_reset() + pcv.color_adjustment_shader_enabled = False - found = False - for k, v in cls.cache.items(): - if(k == uuid): - found = True - break + if('extra' in c.keys()): + del c['extra'] - if(not found): - # if not found, it should be first time called.. - log("draw_update: uuid not found in cache, updating..", 1, prefix='>>>', ) - # return - cls.update(o, uuid, ) - return + PCVManager.update(pcv.uuid, vs, ns, cs, ) - ci = cls.cache[uuid]['draw'] = do_draw - # cls.update(uuid) - cls.render(o) + return {'FINISHED'} + + +class PCV_OT_clip_planes_from_bbox(Operator): + bl_idname = "point_cloud_visualizer.clip_planes_from_bbox" + bl_label = "Set Clip Planes From Object Bounding Box" + bl_description = "Set clip planes from object bounding box" @classmethod - def generate_psys(cls, o, psys_slot, max_points, color_source, color_constant, use_face_area, use_material_factors, ): - log("generate_psys", prefix='>>>', ) + def poll(cls, context): + if(context.object is None): + return False - # FIXME: this should be created just once and passed to next call in the same update.. possible optimization? - depsgraph = bpy.context.evaluated_depsgraph_get() + pcv = context.object.point_cloud_visualizer + ok = False - o = o.evaluated_get(depsgraph) - # why can particle systems have the same name? WHY? NOTHING else works like that in blender - # psys = o.particle_systems[psys.name] - psys = o.particle_systems[psys_slot] - settings = psys.settings - - if(settings.render_type == 'COLLECTION'): - collection = settings.instance_collection - cos = collection.objects - fragments = [] - fragments_indices = {} - for i, co in enumerate(cos): - no_geometry = False - if(co.type not in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): - # self.report({'ERROR'}, "Object does not have geometry data.") - # return {'CANCELLED'} - # NOTE: handle no mesh objects by generating single vertex in object origin (0,0,0) - no_geometry = True - if(no_geometry): - fragments.append(( - np.array(((0.0, 0.0, 0.0, ), ), dtype=np.float32, ), - np.array(((0.0, 0.0, 1.0, ), ), dtype=np.float32, ), - np.array(((1.0, 0.0, 1.0, ), ), dtype=np.float32, ), - ), ) - fragments_indices[co.name] = (i, co, ) - else: - # extract points - # sampler = PCVIVDraftWeightedFixedCountNumpySampler(bpy.context, co, count=max_points, colorize='VIEWPORT_DISPLAY_COLOR', ) - # sampler = PCVIVDraftWeightedFixedCountNumpySampler(bpy.context, co, count=max_points, colorize=color_source, constant_color=color_constant, ) - sampler = PCVIVDraftWeightedFixedCountNumpyWeightedColorsSampler(bpy.context, co, count=max_points, colorize=color_source, constant_color=color_constant, use_face_area=use_face_area, use_material_factors=use_material_factors, ) - # store - fragments.append((sampler.vs, sampler.ns, sampler.cs, )) - # FIXME: better not to access data by object name, find something different - fragments_indices[co.name] = (i, co, ) - - # process all instances, transform fragment and store - all_frags = [] - # loop over instances - for object_instance in depsgraph.object_instances: - obj = object_instance.object - # if it is from psys - if(object_instance.particle_system == psys): - # and it is instance - if(object_instance.is_instance): - # get matrix - m = object_instance.matrix_world - # unapply emitter matrix - m = o.matrix_world.inverted() @ m - # get correct fragment - i, _ = fragments_indices[obj.name] - fvs, fns, fcs = fragments[i] - # transform - fvs.shape = (-1, 3) - fvs = np.c_[fvs, np.ones(fvs.shape[0])] - fvs = np.dot(m, fvs.T)[0:3].T.reshape((-1)) - fvs.shape = (-1, 3) - # transform also normals - _, rot, _ = m.decompose() - rmat = rot.to_matrix().to_4x4() - fns.shape = (-1, 3) - fns = np.c_[fns, np.ones(fns.shape[0])] - fns = np.dot(rmat, fns.T)[0:3].T.reshape((-1)) - fns.shape = (-1, 3) - # store - all_frags.append((fvs, fns, fcs, )) - - # join all frags - if(len(all_frags) == 0): - # vs = [] - # ns = [] - # cs = [] - vs = np.zeros(0, dtype=np.float32, ) - ns = np.zeros(0, dtype=np.float32, ) - cs = np.zeros(0, dtype=np.float32, ) - else: - vs = np.concatenate([i[0] for i in all_frags], axis=0, ) - ns = np.concatenate([i[1] for i in all_frags], axis=0, ) - cs = np.concatenate([i[2] for i in all_frags], axis=0, ) - - elif(settings.render_type == 'OBJECT'): - co = settings.instance_object - if(co.type not in ('MESH', 'CURVE', 'SURFACE', 'FONT', )): - raise Exception("Object does not have geometry data.") - # extract points - # sampler = PCVIVDraftWeightedFixedCountNumpySampler(bpy.context, co, count=max_points, colorize='VIEWPORT_DISPLAY_COLOR', ) - # sampler = PCVIVDraftWeightedFixedCountNumpySampler(bpy.context, co, count=max_points, colorize=color_source, constant_color=color_constant, ) - sampler = PCVIVDraftWeightedFixedCountNumpyWeightedColorsSampler(bpy.context, co, count=max_points, colorize=color_source, constant_color=color_constant, use_face_area=use_face_area, use_material_factors=use_material_factors, ) - ofvs = sampler.vs - ofns = sampler.ns - ofcs = sampler.cs - - all_frags = [] - for object_instance in depsgraph.object_instances: - obj = object_instance.object - if(object_instance.particle_system == psys): - if(object_instance.is_instance): - m = object_instance.matrix_world - m = o.matrix_world.inverted() @ m - fvs = ofvs[:] - fvs.shape = (-1, 3) - fvs = np.c_[fvs, np.ones(fvs.shape[0])] - fvs = np.dot(m, fvs.T)[0:3].T.reshape((-1)) - fvs.shape = (-1, 3) - _, rot, _ = m.decompose() - rmat = rot.to_matrix().to_4x4() - fns = ofns[:] - fns.shape = (-1, 3) - fns = np.c_[fns, np.ones(fns.shape[0])] - fns = np.dot(rmat, fns.T)[0:3].T.reshape((-1)) - fns.shape = (-1, 3) - all_frags.append((fvs, fns, ofcs, )) - - if(len(all_frags) == 0): - vs = np.zeros(0, dtype=np.float32, ) - ns = np.zeros(0, dtype=np.float32, ) - cs = np.zeros(0, dtype=np.float32, ) - else: - vs = np.concatenate([i[0] for i in all_frags], axis=0, ) - ns = np.concatenate([i[1] for i in all_frags], axis=0, ) - cs = np.concatenate([i[2] for i in all_frags], axis=0, ) - - else: - # just generate pink points - l = len(psys.particles) - vs = np.zeros((l * 3), dtype=np.float32, ) - psys.particles.foreach_get('location', vs, ) - vs.shape = (l, 3) - - m = o.matrix_world.inverted() - vs.shape = (-1, 3) - vs = np.c_[vs, np.ones(vs.shape[0])] - vs = np.dot(m, vs.T)[0:3].T.reshape((-1)) - vs.shape = (-1, 3) - - ns = np.zeros((l * 3), dtype=np.float32, ) - # NOTE: what should i consider as normal in particles? rotation? velocity? sometimes is rotation just identity quaternion, do i know nothing about basics? or just too tired? maybe both.. - psys.particles.foreach_get('velocity', ns, ) - _, rot, _ = m.decompose() - rmat = rot.to_matrix().to_4x4() - ns.shape = (-1, 3) - ns = np.c_[ns, np.ones(ns.shape[0])] - ns = np.dot(rmat, ns.T)[0:3].T.reshape((-1)) - ns.shape = (-1, 3) - - cs = np.column_stack((np.full(l, 1.0, dtype=np.float32, ), - np.full(l, 0.0, dtype=np.float32, ), - np.full(l, 1.0, dtype=np.float32, ), )) + bbo = pcv.clip_planes_from_bbox_object + if(bbo is not None): + ok = True - return vs, ns, cs + return ok - @classmethod - def render(cls, o, ): - # if(not cls.initialized): - # return - - log("render", prefix='>>>', ) - - def pre_generate(o, psys, ): - dts = None - dt = None - psys_collection = None - psys_object = None - settings = psys.settings - if(settings.render_type == 'COLLECTION'): - psys_collection = settings.instance_collection - dts = [] - for co in psys_collection.objects: - dts.append((co, co.display_type, )) - co.display_type = 'BOUNDS' - elif(settings.render_type == 'OBJECT'): - psys_object = settings.instance_object - dt = psys_object.display_type - psys_object.display_type = 'BOUNDS' - settings.display_method = 'RENDER' - - mod = None - mview = None - for m in o.modifiers: - if(m.type == 'PARTICLE_SYSTEM'): - if(m.particle_system == psys): - mod = m - mview = m.show_viewport - m.show_viewport = True - break - - return dts, dt, psys_collection, psys_object, mod, mview - - def post_generate(psys, dts, dt, psys_collection, psys_object, mod, mview, ): - settings = psys.settings - settings.display_method = 'NONE' - if(settings.render_type == 'COLLECTION'): - for co, dt in dts: - co.display_type = dt - elif(settings.render_type == 'OBJECT'): - psys_object.display_type = dt - mod.show_viewport = mview - - a = [] - for i, psys in enumerate(o.particle_systems): - _t = time.time() - - # pcv = o.point_cloud_visualizer - pcviv = psys.settings.pcv_instance_visualizer - if(pcviv.uuid not in cls.cache.keys()): - continue - - ci = cls.cache[pcviv.uuid] - if(ci['dirty']): - log("render: psys is dirty", 1, prefix='>>>', ) - - dts, dt, psys_collection, psys_object, mod, mview = pre_generate(o, psys, ) - vs, ns, cs = cls.generate_psys(o, i, pcviv.max_points, pcviv.color_source, pcviv.color_constant, pcviv.use_face_area, pcviv.use_material_factors, ) - post_generate(psys, dts, dt, psys_collection, psys_object, mod, mview, ) - ci['vs'] = vs - ci['ns'] = ns - ci['cs'] = cs - - # FIXME: PCV handles different shaders in a kinda messy way, would be nice to rewrite PCVManager.render to be more flexible, get rid of basic shader auto-creation, store extra shaders in the same way as default one, get rid of booleans for enabling extra shaders and make it to enum, etc.. big task, but it will make next customization much easier and simpler.. - # sz = np.full(len(vs), pcviv.point_size, dtype=np.int8, ) - sz = np.full(len(vs), pcviv.point_size, dtype=np.int, ) - ci['sz'] = sz - - szf = np.full(len(vs), pcviv.point_size_f, dtype=np.float32, ) - ci['szf'] = szf - - ci['dirty'] = False - - if(ci['draw']): - log("render: psys is marked to draw", 1, prefix='>>>', ) - - a.append((ci['vs'], ci['ns'], ci['cs'], ci['sz'], ci['szf'], )) - - _d = datetime.timedelta(seconds=time.time() - _t) - pcviv.debug_update = "last update completed in {}".format(_d) - - _t = time.time() - vs = [] - ns = [] - cs = [] - sz = [] - szf = [] - av = [] - an = [] - ac = [] - az = [] - azf = [] - for v, n, c, s, f in a: - if(len(v) == 0): - # skip systems with zero particles - continue - av.append(v) - an.append(n) - ac.append(c) - az.append(s) - azf.append(f) - if(len(av) > 0): - vs = np.concatenate(av, axis=0, ) - ns = np.concatenate(an, axis=0, ) - cs = np.concatenate(ac, axis=0, ) - sz = np.concatenate(az, axis=0, ) - szf = np.concatenate(azf, axis=0, ) - - log("render: drawing..", 1, prefix='>>>', ) - c = PCVIV2Control(o) - c.draw(vs, ns, cs, sz, szf, ) + def execute(self, context): + pcv = context.object.point_cloud_visualizer + bbo = pcv.clip_planes_from_bbox_object - _d = datetime.timedelta(seconds=time.time() - _t) - pcv = o.point_cloud_visualizer - pcv.pcviv_debug_draw = "last draw completed in {}".format(_d) - - @classmethod - def _redraw_view_3d(cls): - for window in bpy.context.window_manager.windows: - for area in window.screen.areas: - if(area.type == 'VIEW_3D'): - area.tag_redraw() - - -class PCVIV2Control(PCVControl): - def __init__(self, o, ): - super(PCVIV2Control, self, ).__init__(o, ) - # pcv = o.point_cloud_visualizer - # pcv.dev_minimal_shader_variable_size_enabled = True - - def draw(self, vs=None, ns=None, cs=None, sz=None, szf=None, ): - o = self.o - pcv = o.point_cloud_visualizer + mw = bbo.matrix_world + vs = [mw @ Vector(v) for v in bbo.bound_box] - # pcv.dev_minimal_shader_variable_size_enabled = True - # pcv.dev_minimal_shader_variable_size_and_depth_enabled = True - # pcv.dev_rich_billboard_point_cloud_enabled = True + # 0 front left down + # 1 front left up + # 2 back left up + # 3 back left down + # 4 front right down + # 5 front right up + # 6 back right up + # 7 back right down + # 2-----------6 + # / /| + # 1-----------5 | + # | | | + # | | | + # | 3 | 7 + # | | / + # 0-----------4 - # FIXME: this is also stupid - if(pcv.dev_minimal_shader_variable_size_enabled or pcv.dev_minimal_shader_variable_size_and_depth_enabled or pcv.dev_rich_billboard_point_cloud_enabled or pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - pass - else: - # NOTE: this is what will be default draw type, should i put preferences for it somewhere? - # pcv.dev_minimal_shader_variable_size_and_depth_enabled = True - - # lets go with the best one - pcv.dev_rich_billboard_point_cloud_enabled = True + fs = ( + (0, 4, 5, 1), # front + (3, 2, 6, 7), # back + (1, 5, 6, 2), # top + (0, 3, 7, 4), # bottom + (0, 1, 2, 3), # left + (4, 7, 6, 5), # right + ) - # check if object has been used before, i.e. has uuid and uuid item is in cache - if(pcv.uuid != "" and pcv.runtime): - # was used or blend was saved after it was used and uuid is saved from last time, check cache - if(pcv.uuid in PCVManager.cache): - # cache item is found, object has been used before - self._update(vs, ns, cs, sz, szf, ) - return - # otherwise setup as new + quads = [[vs[fs[i][j]] for j in range(4)] for i in range(6)] + ns = [mathutils.geometry.normal(quads[i]) for i in range(6)] + for i in range(6): + # FIXME: if i need to do this, it is highly probable i have something wrong.. somewhere.. + ns[i].negate() - u = str(uuid.uuid1()) - # use that as path, some checks wants this not empty - filepath = u + ds = [] + for i in range(6): + v = quads[i][0] + n = ns[i] + d = mathutils.geometry.distance_point_to_plane(Vector(), v, n) + ds.append(d) - # validate/prepare input data - vs, ns, cs, points, has_normals, has_colors = self._prepare(vs, ns, cs) - n = len(vs) + a = [ns[i].to_tuple() + (ds[i], ) for i in range(6)] + pcv.clip_plane0 = a[0] + pcv.clip_plane1 = a[1] + pcv.clip_plane2 = a[2] + pcv.clip_plane3 = a[3] + pcv.clip_plane4 = a[4] + pcv.clip_plane5 = a[5] - # TODO: validate also sz array + pcv.clip_shader_enabled = True + pcv.clip_plane0_enabled = True + pcv.clip_plane1_enabled = True + pcv.clip_plane2_enabled = True + pcv.clip_plane3_enabled = True + pcv.clip_plane4_enabled = True + pcv.clip_plane5_enabled = True - # build cache dict - d = {} - d['uuid'] = u - d['filepath'] = filepath - d['points'] = points + return {'FINISHED'} + + +class PCV_OT_clip_planes_reset(Operator): + bl_idname = "point_cloud_visualizer.clip_planes_reset" + bl_label = "Reset Clip Planes" + bl_description = "Reset all clip planes" + + @classmethod + def poll(cls, context): + if(context.object is None): + return False - # but because colors i just stored in uint8, store them also as provided to enable reload operator - cs_orig = np.column_stack((cs[:, 0], cs[:, 1], cs[:, 2], np.ones(n), )) - cs_orig = cs_orig.astype(np.float32) - d['colors_original'] = cs_orig + pcv = context.object.point_cloud_visualizer + ok = True - d['stats'] = n - d['vertices'] = vs - d['colors'] = cs - d['normals'] = ns - d['length'] = n - dp = pcv.display_percent - l = int((n / 100) * dp) - if(dp >= 99): - l = n - d['display_length'] = l - d['current_display_length'] = l - # d['illumination'] = pcv.illumination - d['illumination'] = False - - # if(pcv.dev_minimal_shader_variable_size_enabled): - # shader = GPUShader(PCVShaders.vertex_shader_minimal_variable_size, PCVShaders.fragment_shader_minimal_variable_size, ) - # batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "size": sz[:l], }) - # else: - # shader = GPUShader(PCVShaders.vertex_shader_simple, PCVShaders.fragment_shader_simple) - # batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], }) - shader = GPUShader(PCVShaders.vertex_shader_simple, PCVShaders.fragment_shader_simple) - batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], }) - d['shader'] = shader - d['batch'] = batch - d['ready'] = True - d['draw'] = False - d['kill'] = False - d['object'] = o - d['name'] = o.name + return ok + + def execute(self, context): + pcv = context.object.point_cloud_visualizer - d['extra'] = {} - if(pcv.dev_minimal_shader_variable_size_enabled): - e_shader = GPUShader(PCVShaders.vertex_shader_minimal_variable_size, PCVShaders.fragment_shader_minimal_variable_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "size": sz[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['MINIMAL_VARIABLE_SIZE'] = extra - if(pcv.dev_minimal_shader_variable_size_and_depth_enabled): - # TODO: do the same for the other shader until i decide which is better.. - e_shader = GPUShader(PCVShaders.vertex_shader_minimal_variable_size_and_depth, PCVShaders.fragment_shader_minimal_variable_size_and_depth, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "size": sz[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['MINIMAL_VARIABLE_SIZE_AND_DEPTH'] = extra - if(pcv.dev_rich_billboard_point_cloud_enabled): - # FIXME: this is getting ridiculous - e_shader = GPUShader(PCVShaders.billboard_vertex_with_depth_and_size, PCVShaders.billboard_fragment_with_depth_and_size, geocode=PCVShaders.billboard_geometry_with_depth_and_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "sizef": szf[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['RICH_BILLBOARD'] = extra + pcv.clip_planes_from_bbox_object = None - if(pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - # FIXME: this is getting ridiculous - e_shader = GPUShader(PCVShaders.billboard_vertex_with_no_depth_and_size, PCVShaders.billboard_fragment_with_no_depth_and_size, geocode=PCVShaders.billboard_geometry_with_no_depth_and_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "sizef": szf[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['RICH_BILLBOARD_NO_DEPTH'] = extra + z = (0.0, 0.0, 0.0, 0.0, ) + pcv.clip_plane0 = z + pcv.clip_plane1 = z + pcv.clip_plane2 = z + pcv.clip_plane3 = z + pcv.clip_plane4 = z + pcv.clip_plane5 = z - # d['extra_data'] = { - # 'sizes': sz, - # 'sizesf': szf, - # } + pcv.clip_shader_enabled = False + pcv.clip_plane0_enabled = False + pcv.clip_plane1_enabled = False + pcv.clip_plane2_enabled = False + pcv.clip_plane3_enabled = False + pcv.clip_plane4_enabled = False + pcv.clip_plane5_enabled = False - # set properties - pcv.uuid = u - pcv.filepath = filepath - pcv.has_normals = has_normals - pcv.has_vcols = has_colors - pcv.runtime = True + return {'FINISHED'} + + +class PCV_OT_clip_planes_from_camera_view(Operator): + bl_idname = "point_cloud_visualizer.clip_planes_from_camera_view" + bl_label = "Set Clip Planes From Camera View" + bl_description = "Set clip planes from active camera view" + + @classmethod + def poll(cls, context): + if(context.object is None): + return False - PCVManager.add(d) + pcv = context.object.point_cloud_visualizer + ok = False - # mark to draw - c = PCVManager.cache[pcv.uuid] - c['draw'] = True + s = bpy.context.scene + o = s.camera + if(o): + ok = True - self._redraw() + return ok - def _update(self, vs, ns, cs, sz, szf, ): - o = self.o - pcv = o.point_cloud_visualizer + def execute(self, context): + pcv = context.object.point_cloud_visualizer - # pcv.dev_minimal_shader_variable_size_enabled = True - # pcv.dev_minimal_shader_variable_size_and_depth_enabled = True - # pcv.dev_rich_billboard_point_cloud_enabled = True + o = bpy.context.object - # FIXME: this is also stupid - if(pcv.dev_minimal_shader_variable_size_enabled or pcv.dev_minimal_shader_variable_size_and_depth_enabled or pcv.dev_rich_billboard_point_cloud_enabled or pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - pass + s = bpy.context.scene + c = s.camera + cd = c.data + rx = s.render.resolution_x + ry = s.render.resolution_y + w = 0.5 * cd.sensor_width / cd.lens + if(rx > ry): + x = w + y = w * ry / rx else: - # NOTE: this is what will be default draw type, should i put preferences for it somewhere? - # pcv.dev_minimal_shader_variable_size_and_depth_enabled = True - - # lets go with the best one - pcv.dev_rich_billboard_point_cloud_enabled = True + x = w * rx / ry + y = w - # validate/prepare input data - vs, ns, cs, points, has_normals, has_colors = self._prepare(vs, ns, cs) - n = len(vs) + lr = Vector((x, -y, -1.0, )) + ur = Vector((x, y, -1.0, )) + ll = Vector((-x, -y, -1.0, )) + ul = Vector((-x, y, -1.0, )) - d = PCVManager.cache[pcv.uuid] - d['points'] = points + z = Vector() + n0 = mathutils.geometry.normal((z, lr, ur)) + n1 = mathutils.geometry.normal((z, ur, ul)) + n2 = mathutils.geometry.normal((z, ul, ll)) + n3 = mathutils.geometry.normal((z, ll, lr)) - # kill normals, might not be no longer valid, it will be recreated later - if('vertex_normals' in d.keys()): - del d['vertex_normals'] + n0.negate() + n1.negate() + n2.negate() + n3.negate() - # but because colors i just stored in uint8, store them also as provided to enable reload operator - cs_orig = np.column_stack((cs[:, 0], cs[:, 1], cs[:, 2], np.ones(n), )) - cs_orig = cs_orig.astype(np.float32) - d['colors_original'] = cs_orig + m = c.matrix_world + l, r, _ = m.decompose() + rm = r.to_matrix().to_4x4() + lrm = Matrix.Translation(l).to_4x4() @ rm - d['stats'] = n - d['vertices'] = vs - d['colors'] = cs - d['normals'] = ns - d['length'] = n - dp = pcv.display_percent - l = int((n / 100) * dp) - if(dp >= 99): - l = n - d['display_length'] = l - d['current_display_length'] = l - # d['illumination'] = pcv.illumination - d['illumination'] = False - # if(pcv.illumination): - # shader = GPUShader(PCVShaders.vertex_shader_illumination, PCVShaders.fragment_shader_illumination) - # batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "normal": ns[:l], }) - # else: - # shader = GPUShader(PCVShaders.vertex_shader_simple, PCVShaders.fragment_shader_simple) - # batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], }) - shader = GPUShader(PCVShaders.vertex_shader_simple, PCVShaders.fragment_shader_simple) - batch = batch_for_shader(shader, 'POINTS', {"position": vs[:l], "color": cs[:l], }) - d['shader'] = shader - d['batch'] = batch + omi = o.matrix_world.inverted() + _, r, _ = omi.decompose() + orm = r.to_matrix().to_4x4() - pcv.has_normals = has_normals - pcv.has_vcols = has_colors + n0 = orm @ rm @ n0 + n1 = orm @ rm @ n1 + n2 = orm @ rm @ n2 + n3 = orm @ rm @ n3 - d['extra'] = {} - if(pcv.dev_minimal_shader_variable_size_enabled): - e_shader = GPUShader(PCVShaders.vertex_shader_minimal_variable_size, PCVShaders.fragment_shader_minimal_variable_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "size": sz[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['MINIMAL_VARIABLE_SIZE'] = extra - if(pcv.dev_minimal_shader_variable_size_and_depth_enabled): - # TODO: do the same for the other shader until i decide which is better.. - e_shader = GPUShader(PCVShaders.vertex_shader_minimal_variable_size_and_depth, PCVShaders.fragment_shader_minimal_variable_size_and_depth, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "size": sz[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['MINIMAL_VARIABLE_SIZE_AND_DEPTH'] = extra - if(pcv.dev_rich_billboard_point_cloud_enabled): - # FIXME: this is getting ridiculous - e_shader = GPUShader(PCVShaders.billboard_vertex_with_depth_and_size, PCVShaders.billboard_fragment_with_depth_and_size, geocode=PCVShaders.billboard_geometry_with_depth_and_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "sizef": szf[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['RICH_BILLBOARD'] = extra + v0 = omi @ lrm @ lr + v1 = omi @ lrm @ ur + v2 = omi @ lrm @ ul + v3 = omi @ lrm @ ll - if(pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - # FIXME: this is getting ridiculous - e_shader = GPUShader(PCVShaders.billboard_vertex_with_no_depth_and_size, PCVShaders.billboard_fragment_with_no_depth_and_size, geocode=PCVShaders.billboard_geometry_with_no_depth_and_size, ) - e_batch = batch_for_shader(e_shader, 'POINTS', {"position": vs[:l], "color": cs[:l], "sizef": szf[:l], }) - extra = { - 'shader': e_shader, - 'batch': e_batch, - 'sizes': sz, - 'sizesf': szf, - 'length': l, - } - d['extra']['RICH_BILLBOARD_NO_DEPTH'] = extra + d0 = mathutils.geometry.distance_point_to_plane(Vector(), v0, n0) + d1 = mathutils.geometry.distance_point_to_plane(Vector(), v1, n1) + d2 = mathutils.geometry.distance_point_to_plane(Vector(), v2, n2) + d3 = mathutils.geometry.distance_point_to_plane(Vector(), v3, n3) - # d['extra_data'] = { - # 'sizes': sz, - # 'sizesf': szf, - # } + # TODO: add plane behind camera (not much needed anyway, but for consistency), but more important, add plane in clipping distance set on camera + # TODO: ORTHO camera does not work - c = PCVManager.cache[pcv.uuid] - c['draw'] = True + pcv.clip_plane0 = n0.to_tuple() + (d0, ) + pcv.clip_plane1 = n1.to_tuple() + (d1, ) + pcv.clip_plane2 = n2.to_tuple() + (d2, ) + pcv.clip_plane3 = n3.to_tuple() + (d3, ) + pcv.clip_plane4 = (0.0, 0.0, 0.0, 0.0, ) + pcv.clip_plane5 = (0.0, 0.0, 0.0, 0.0, ) + + pcv.clip_shader_enabled = True + pcv.clip_plane0_enabled = True + pcv.clip_plane1_enabled = True + pcv.clip_plane2_enabled = True + pcv.clip_plane3_enabled = True + pcv.clip_plane4_enabled = False + pcv.clip_plane5_enabled = False - self._redraw() + return {'FINISHED'} class PCV_PT_panel(Panel): @@ -11599,7 +9839,7 @@ def draw(self, context): c.prop(pcv, 'generate_seed') c.operator('point_cloud_visualizer.generate_volume_from_mesh') - sub.separator() + # sub.separator() """ c.separator() @@ -11661,419 +9901,14 @@ def draw(self, context): c.separator() """ - sub.label(text="Numpy Vertices And Normals Transform") - c = sub.column() - c.prop(pcv, 'dev_transform_normals_target_object') - c.operator('point_cloud_visualizer.pcviv_dev_transform_normals') - - sub.label(text="Clip To Active Camera Cone") - c = sub.column() - c.operator('point_cloud_visualizer.clip_planes_from_camera_view') - - -class PCVIV2RuntimeSettings(): - enabled = False - - -class PCVIV2_PT_panel(Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = "View" - bl_label = "Point Cloud Instance Visualizer" - bl_parent_id = "PCV_PT_panel" - bl_options = {'DEFAULT_CLOSED'} - - @classmethod - def poll(cls, context): - # if(not debug_mode()): - # return False - - if(not PCVIV2RuntimeSettings.enabled): - return False - - o = context.active_object - if(o is None): - return False - - if(o): - pcv = o.point_cloud_visualizer - if(pcv.edit_is_edit_mesh): - return False - if(pcv.edit_initialized): - return False - return True - - def draw_header(self, context): - pcv = context.object.point_cloud_visualizer - l = self.layout - l.label(text='', icon='MOD_PARTICLE_INSTANCE', ) - - def draw(self, context): - """ - def prop_name(cls, prop, colon=False, ): - for p in cls.bl_rna.properties: - if(p.identifier == prop): - if(colon): - return "{}:".format(p.name) - return p.name - return '' - - def third_label_two_thirds_prop(cls, prop, uil, ): - f = 0.33 - r = uil.row() - s = r.split(factor=f) - s.label(text=prop_name(cls, prop, True, )) - s = s.split(factor=1.0) - r = s.row() - r.prop(cls, prop, text='', ) - - def third_label_two_thirds_prop_search(cls, prop, cls2, prop2, uil, ): - f = 0.33 - r = uil.row() - s = r.split(factor=f) - s.label(text=prop_name(cls, prop, True, )) - s = s.split(factor=1.0) - r = s.row() - r.prop_search(cls, prop, cls2, prop2, text='', ) - - def third_label_two_thirds_prop_search_aligned(cls, prop, cls2, prop2, uil, ): - f = 0.33 - r = uil.row(align=True) - s = r.split(factor=f, align=True) - s.label(text=prop_name(cls, prop, True, )) - s = s.split(factor=1.0, align=True) - r = s.row(align=True) - r.prop_search(cls, prop, cls2, prop2, text='', ) - - def third_label_two_thirds_prop_enum_expand(cls, prop, uil, ): - f = 0.33 - r = uil.row() - s = r.split(factor=f) - s.label(text=prop_name(cls, prop, True, )) - s = s.split(factor=1.0) - r = s.row() - r.prop(cls, prop, expand=True, ) - """ - - o = context.object - pcv = o.point_cloud_visualizer - l = self.layout - c = l.column() - - if(not PCVIV2Manager.initialized): - - r = c.row() - r.alignment = 'CENTER' - r.label(text='Not initialized..', icon='ERROR', ) - c.separator() - - r = c.row(align=True) - r.operator('point_cloud_visualizer.pcviv_init') - # if(not debug_mode()): - # if(PCVIV2Manager.initialized): - # r.enabled = False - - else: - - if(debug_mode()): - r = c.row(align=True) - r.operator('point_cloud_visualizer.pcviv_init') - c.separator() - - if(len(o.particle_systems) == 0): - b = c.box() - b.label(text='No Particle Systems..', icon='ERROR', ) - - for psys in o.particle_systems: - pcviv = psys.settings.pcv_instance_visualizer - b = c.box() - - if(not pcviv.subpanel_opened): - r = b.row() - rr = r.row(align=True) - # twice, because i want clicking on psys name to be possible - rr.prop(pcviv, 'subpanel_opened', icon='TRIA_DOWN' if pcviv.subpanel_opened else 'TRIA_RIGHT', icon_only=True, emboss=False, ) - rr.prop(pcviv, 'subpanel_opened', icon='PARTICLES', icon_only=True, emboss=False, text=psys.settings.name, ) - rr = r.row() - # rr.prop(pcviv, 'draw', text='', toggle=True, icon_only=True, icon='HIDE_OFF' if pcviv.draw else 'HIDE_ON', ) - rr.prop(pcviv, 'draw', text='', toggle=True, icon_only=True, icon='RESTRICT_VIEW_OFF' if pcviv.draw else 'RESTRICT_VIEW_ON', ) - # update, alert when dirty. in fact it's just before first draw - ccc = rr.column(align=True) - rrr = ccc.row(align=True) - if(pcviv.draw): - alert = False - if(pcviv.uuid in PCVIV2Manager.cache.keys()): - if(PCVIV2Manager.cache[pcviv.uuid]['dirty']): - alert = True - else: - alert = True - rrr.alert = alert - else: - rrr.enabled = False - rrr.operator('point_cloud_visualizer.pcviv_update').uuid = pcviv.uuid - cccc = rrr.row(align=True) - cccc.alert = False - # cccc.prop(pcviv, 'use_face_area', icon_only=True, icon='FACESEL', toggle=True, text='', ) - cccc.prop(pcviv, 'use_face_area', icon_only=True, icon='FACE_MAPS', toggle=True, text='', ) - cccc.prop(pcviv, 'use_material_factors', icon_only=True, icon='MATERIAL', toggle=True, text='', ) - else: - r = b.row(align=True) - # twice, because i want clicking on psys name to be possible - r.prop(pcviv, 'subpanel_opened', icon='TRIA_DOWN' if pcviv.subpanel_opened else 'TRIA_RIGHT', icon_only=True, emboss=False, ) - r.prop(pcviv, 'subpanel_opened', icon='PARTICLES', icon_only=True, emboss=False, text=psys.settings.name, ) - # options - cc = b.column(align=True) - cc.prop(pcviv, 'max_points') - if(pcv.dev_rich_billboard_point_cloud_enabled or pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - cc.prop(pcviv, 'point_size_f') - else: - cc.prop(pcviv, 'point_size') - _r = cc.row(align=True) - if(pcviv.color_source == 'CONSTANT'): - _s = _r.split(factor=0.75, align=True, ) - _s.prop(pcviv, 'color_source', ) - _s = _s.split(factor=1.0, align=True, ) - _s.prop(pcviv, 'color_constant', text='', ) - - else: - _r.prop(pcviv, 'color_source', ) - # update - cc = b.column() - r = cc.row() - # r.prop(pcviv, 'draw', text='', toggle=True, icon_only=True, icon='HIDE_OFF' if pcviv.draw else 'HIDE_ON', ) - r.prop(pcviv, 'draw', text='', toggle=True, icon_only=True, icon='RESTRICT_VIEW_OFF' if pcviv.draw else 'RESTRICT_VIEW_ON', ) - ccc = r.column(align=True) - rrr = ccc.row(align=True) - if(pcviv.draw): - alert = False - if(pcviv.uuid in PCVIV2Manager.cache.keys()): - if(PCVIV2Manager.cache[pcviv.uuid]['dirty']): - # if in cache and is dirty - alert = True - else: - # or not in cache at all, ie. not processed yet - alert = True - rrr.alert = alert - else: - rrr.enabled = False - rrr.operator('point_cloud_visualizer.pcviv_update').uuid = pcviv.uuid - cccc = rrr.row(align=True) - cccc.alert = False - # cccc.prop(pcviv, 'use_face_area', icon_only=True, icon='FACESEL', toggle=True, text='', ) - cccc.prop(pcviv, 'use_face_area', icon_only=True, icon='FACE_MAPS', toggle=True, text='', ) - cccc.prop(pcviv, 'use_material_factors', icon_only=True, icon='MATERIAL', toggle=True, text='', ) - # if(debug_mode()): - # if(pcviv.debug_update == ''): - # cc.label(text='(debug: {})'.format('n/a', )) - # else: - # cc.label(text='(debug: {})'.format(pcviv.debug_update, )) - - if(debug_mode()): - if(pcviv.debug_update == ''): - b.label(text='(debug: {})'.format('n/a', )) - else: - b.label(text='(debug: {})'.format(pcviv.debug_update, )) - - c.separator() - r = c.row(align=True) - r.operator('point_cloud_visualizer.pcviv_update_all') - r.operator('point_cloud_visualizer.pcviv_reset') - - c.separator() - r = c.row() - r.alignment = 'RIGHT' - r.label(text='Powered by: Point Cloud Visualizer') - - -class PCVIV2_UL_materials(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, ): - pcvivgp = item.pcv_instance_visualizer - - r = layout.row(align=True) - s = r.split(factor=0.5, align=True, ) - s.label(text=item.name, icon='MATERIAL', ) - s = s.split(factor=0.8, align=True, ) - s.prop(pcvivgp, 'factor', text='', ) - s = s.split(factor=1.0, align=True, ) - s.prop(item, 'diffuse_color', text='', ) - - -class PCVIV2_PT_generator(Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = "View" - bl_label = "Generator Options" - bl_parent_id = "PCVIV2_PT_panel" - bl_options = {'DEFAULT_CLOSED'} - - @classmethod - def poll(cls, context): - o = context.active_object - if(o is None): - return False - - if(o): - pcv = o.point_cloud_visualizer - if(pcv.edit_is_edit_mesh): - return False - if(pcv.edit_initialized): - return False - - if(not PCVIV2Manager.initialized): - return False - - return True - - def draw(self, context): - o = context.object - pcv = o.point_cloud_visualizer - l = self.layout - c = l.column() - # c.label(text="Material Point Probability:") - c.label(text="Point probability per material:") - c.template_list("PCVIV2_UL_materials", "", bpy.data, "materials", pcv, "pcviv_material_list_active_index") - - -class PCVIV2_PT_display(Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = "View" - bl_label = "Display Options" - bl_parent_id = "PCVIV2_PT_panel" - bl_options = {'DEFAULT_CLOSED'} - - @classmethod - def poll(cls, context): - o = context.active_object - if(o is None): - return False - - if(o): - pcv = o.point_cloud_visualizer - if(pcv.edit_is_edit_mesh): - return False - if(pcv.edit_initialized): - return False - - if(not PCVIV2Manager.initialized): - return False - - return True - - def draw(self, context): - o = context.object - pcv = o.point_cloud_visualizer - l = self.layout - c = l.column() - c.label(text="Shader:") - r = c.row(align=True) - r.prop(pcv, 'dev_minimal_shader_variable_size_enabled', toggle=True, text="Basic", ) - r.prop(pcv, 'dev_minimal_shader_variable_size_and_depth_enabled', toggle=True, text="Depth", ) - r.prop(pcv, 'dev_rich_billboard_point_cloud_no_depth_enabled', toggle=True, text="Billboard", ) - r.prop(pcv, 'dev_rich_billboard_point_cloud_enabled', toggle=True, text="Depth Billboard", ) - if(pcv.dev_minimal_shader_variable_size_and_depth_enabled): - cc = c.column(align=True) - cc.prop(pcv, 'dev_minimal_shader_variable_size_and_depth_brightness') - cc.prop(pcv, 'dev_minimal_shader_variable_size_and_depth_contrast') - cc.prop(pcv, 'dev_minimal_shader_variable_size_and_depth_blend') - if(pcv.dev_rich_billboard_point_cloud_no_depth_enabled): - cc = c.column(align=True) - cc.prop(pcv, 'dev_rich_billboard_point_cloud_size') - if(pcv.dev_rich_billboard_point_cloud_enabled): - cc = c.column(align=True) - cc.prop(pcv, 'dev_rich_billboard_point_cloud_size') - cc = c.column(align=True) - cc.prop(pcv, 'dev_rich_billboard_depth_brightness') - cc.prop(pcv, 'dev_rich_billboard_depth_contrast') - cc.prop(pcv, 'dev_rich_billboard_depth_blend') - - -class PCVIV2_PT_debug(Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = "View" - bl_label = "PCVIV Debug" - bl_parent_id = "PCVIV2_PT_panel" - bl_options = {'DEFAULT_CLOSED'} - - @classmethod - def poll(cls, context): - o = context.active_object - if(o is None): - return False - - if(not PCVIV2Manager.initialized): - return False - - if(debug_mode()): - return True - - return False - - def draw_header(self, context): - pcv = context.object.point_cloud_visualizer - l = self.layout - l.label(text='', icon='SETTINGS', ) - - def draw(self, context): - o = context.object - pcv = o.point_cloud_visualizer - l = self.layout - c = l.column() - - b = c.box() - c = b.column() - - c.separator() - if(pcv.pcviv_debug_draw != ''): - c.label(text='(debug: {})'.format(pcv.pcviv_debug_draw, )) - c.separator() - - r = c.row(align=True) - # r.operator('point_cloud_visualizer.pcviv_reset') - r.operator('point_cloud_visualizer.pcviv_deinit') - r.operator('point_cloud_visualizer.pcviv_reset_all') - c.separator() - - r = c.row() - r.prop(pcv, 'pcviv_debug_panel_show_info', icon='TRIA_DOWN' if pcv.pcviv_debug_panel_show_info else 'TRIA_RIGHT', icon_only=True, emboss=False, ) - r.label(text="Debug Info") - if(pcv.pcviv_debug_panel_show_info): - cc = c.column() - cc.label(text="object: '{}'".format(o.name)) - cc.label(text="psystem(s): {}".format(len(o.particle_systems))) - cc.scale_y = 0.5 - - c.separator() - - tab = ' ' - - cc = c.column() - cc.label(text="PCVIV2Manager:") - cc.label(text="{}initialized: {}".format(tab, PCVIV2Manager.initialized)) - cc.label(text="{}cache: {} item(s)".format(tab, len(PCVIV2Manager.cache.keys()))) - cc.scale_y = 0.5 - - c.separator() - tab = ' ' - ci = 0 - for k, v in PCVIV2Manager.cache.items(): - b = c.box() - cc = b.column() - cc.scale_y = 0.5 - cc.label(text='item: {}'.format(ci)) - ci += 1 - for l, w in v.items(): - if(type(w) == dict): - cc.label(text='{}{}: {} item(s)'.format(tab, l, len(w.keys()))) - elif(type(w) == np.ndarray or type(w) == list): - cc.label(text='{}{}: {} item(s)'.format(tab, l, len(w))) - else: - cc.label(text='{}{}: {}'.format(tab, l, w)) + # sub.label(text="Numpy Vertices And Normals Transform") + # c = sub.column() + # c.prop(pcv, 'dev_transform_normals_target_object') + # c.operator('point_cloud_visualizer.pcviv_dev_transform_normals') - # and some development shortcuts.. - c.separator() - c.operator('script.reload', text='debug: reload scripts', ) + # sub.label(text="Clip To Active Camera Cone") + # c = sub.column() + # c.operator('point_cloud_visualizer.clip_planes_from_camera_view') class PCV_PT_debug(Panel): @@ -12191,61 +10026,6 @@ def draw(self, context): c.label(text="{}: {}".format('data', '{} item(s)'.format(len(v['data'])))) -class PCVIV2_properties(PropertyGroup): - # to identify object, key for storing cloud in cache, etc. - uuid: StringProperty(default="", options={'HIDDEN', }, ) - - def _draw_update(self, context, ): - PCVIV2Manager.draw_update(context.object, self.uuid, self.draw, ) - - # draw cloud enabled/disabled - draw: BoolProperty(name="Draw", default=True, description="Draw particle instances as point cloud", update=_draw_update, ) - # user can set maximum number of points drawn per instance - max_points: IntProperty(name="Max. Points Per Instance", default=1000, min=1, max=1000000, description="Maximum number of points per instance", ) - # user can set size of points, but it will be only used when minimal shader is active - point_size: IntProperty(name="Size", default=3, min=1, max=10, subtype='PIXEL', description="Point size", ) - # rich billboard shader size - point_size_f: FloatProperty(name="Scale", default=1.0, min=0.0, max=10.0, description="Point scale (shader size * scale)", precision=6, ) - - color_source: EnumProperty(name="Color", items=[('CONSTANT', "Constant Color", "Use constant color value"), - ('VIEWPORT_DISPLAY_COLOR', "Material Viewport Display Color", "Use material viewport display color property"), - ], default='VIEWPORT_DISPLAY_COLOR', description="Color source for generated point cloud", ) - color_constant: FloatVectorProperty(name="Color", description="Constant color", default=(0.7, 0.7, 0.7, ), min=0, max=1, subtype='COLOR', size=3, ) - - def _method_update(self, context, ): - if(not self.use_face_area and not self.use_material_factors): - self.use_face_area = True - - use_face_area: BoolProperty(name="Use Face Area", default=True, description="Use mesh face area as probability factor during point cloud generation", update=_method_update, ) - use_material_factors: BoolProperty(name="Use Material Factors", default=False, description="Use material probability factor during point cloud generation", update=_method_update, ) - - # helper property, draw minimal ui or draw all props - subpanel_opened: BoolProperty(default=False, options={'HIDDEN', }, ) - - # store info how long was last update, generate and store to cache - debug_update: StringProperty(default="", ) - - @classmethod - def register(cls): - bpy.types.ParticleSettings.pcv_instance_visualizer = PointerProperty(type=cls) - - @classmethod - def unregister(cls): - del bpy.types.ParticleSettings.pcv_instance_visualizer - - -class PCVIV2_generator_properties(PropertyGroup): - factor: FloatProperty(name="Factor", default=0.5, min=0.0, max=1.0, precision=3, subtype='FACTOR', description="Probability factor of choosing polygon with this material", ) - - @classmethod - def register(cls): - bpy.types.Material.pcv_instance_visualizer = PointerProperty(type=cls) - - @classmethod - def unregister(cls): - del bpy.types.Material.pcv_instance_visualizer - - class PCV_properties(PropertyGroup): filepath: StringProperty(name="PLY File", default="", description="", ) uuid: StringProperty(default="", options={'HIDDEN', }, ) @@ -12953,7 +10733,6 @@ def _update_panel_bl_category(self, context, ): PCV_PT_edit, PCV_PT_filter, PCV_PT_filter_simplify, PCV_PT_filter_project, PCV_PT_filter_boolean, PCV_PT_filter_remove_color, PCV_PT_filter_merge, PCV_PT_filter_join, PCV_PT_filter_color_adjustment, PCV_PT_render, PCV_PT_convert, PCV_PT_generate, PCV_PT_export, PCV_PT_sequence, PCV_PT_development, - # PCVIV2_PT_panel, PCVIV2_PT_generator, PCVIV2_PT_display, PCVIV2_PT_debug, PCV_PT_debug, ) try: @@ -13036,13 +10815,9 @@ def draw(self, context): def watcher(scene): PCVSequence.deinit() PCVManager.deinit() - # # PCVIVManager.deinit() - # PCVIVManager.reset() - PCVIV2Manager.deinit() classes = ( - PCVIV2_properties, PCVIV2_generator_properties, PCVIV2_UL_materials, PCV_properties, PCV_preferences, PCV_PT_panel, PCV_PT_clip, PCV_PT_edit, @@ -13060,10 +10835,7 @@ def watcher(scene): PCV_PT_development, PCV_OT_generate_volume_point_cloud, - # PCVIV2_PT_panel, PCVIV2_PT_generator, PCVIV2_PT_display, PCVIV2_PT_debug, - # PCVIV2_OT_init, PCVIV2_OT_deinit, PCVIV2_OT_reset, PCVIV2_OT_reset_all, PCVIV2_OT_update, PCVIV2_OT_update_all, - - PCVIV2_OT_dev_transform_normals, PCV_OT_clip_planes_from_bbox, PCV_OT_clip_planes_reset, PCV_OT_clip_planes_from_camera_view, + PCV_OT_clip_planes_from_bbox, PCV_OT_clip_planes_reset, PCV_OT_clip_planes_from_camera_view, PCV_PT_debug, PCV_OT_init, PCV_OT_deinit, PCV_OT_gc, PCV_OT_seq_init, PCV_OT_seq_deinit, @@ -13080,7 +10852,6 @@ def register(): def unregister(): PCVSequence.deinit() PCVManager.deinit() - PCVIV2Manager.deinit() for cls in reversed(classes): bpy.utils.unregister_class(cls)