From 15b57d84e4b504fa9102ee4e21adbde2ca8a7fbd Mon Sep 17 00:00:00 2001 From: Peter Noble Date: Tue, 6 Sep 2016 12:58:35 +0100 Subject: [PATCH 1/4] No radius path following --- cm_channels/__init__.py | 1 + cm_channels/cm_pathChannels.py | 181 +++++++++++++++++++++++++++++++++ cm_simulate.py | 4 +- 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 cm_channels/cm_pathChannels.py diff --git a/cm_channels/__init__.py b/cm_channels/__init__.py index c2eca61..e6dc31f 100644 --- a/cm_channels/__init__.py +++ b/cm_channels/__init__.py @@ -5,5 +5,6 @@ from .cm_crowdChannels import Crowd from .cm_groundChannels import Ground from .cm_formationChannels import Formation +from .cm_pathChannels import Path from .cm_masterChannels import Wrapper diff --git a/cm_channels/cm_pathChannels.py b/cm_channels/cm_pathChannels.py new file mode 100644 index 0000000..652357c --- /dev/null +++ b/cm_channels/cm_pathChannels.py @@ -0,0 +1,181 @@ +import bpy +import mathutils +from mathutils import Vector +Rotation = mathutils.Matrix.Rotation +import math +import bmesh + +from .cm_masterChannels import MasterChannel as Mc + +class Path(Mc): + """Used to access data about paths in the scene""" + def __init__(self, sim): + Mc.__init__(self, sim) + + self.pathObjectCache = {} + self.resultsCache = {} + + def newframe(self): + self.pathObjectCache = {} + self.resultsCache = {} + + def calcPathData(self, pathObject): + if pathObject in self.pathObjectCache: + return self.pathObjectCache[pathObject] + obj = bpy.context.scene.objects[pathObject] + mesh = obj.data + size = len(mesh.vertices) + kd = mathutils.kdtree.KDTree(size) + + for i, v in enumerate(mesh.vertices): + kd.insert(v.co, i) + + kd.balance() + + bm = bmesh.new() + bm.from_mesh(mesh) + + bm.verts.ensure_lookup_table() + + pathMatrixInverse = obj.matrix_world.inverted() + + x, y, z = obj.rotation_euler + z = mathutils.Matrix.Rotation(obj.rotation_euler[2], 4, 'Z') + y = mathutils.Matrix.Rotation(obj.rotation_euler[1], 4, 'Y') + x = mathutils.Matrix.Rotation(obj.rotation_euler[0], 4, 'X') + + rotation = x * y * z + + self.pathObjectCache[pathObject] = (kd, bm, pathMatrixInverse, rotation) + + return kd, bm, pathMatrixInverse, rotation + + def followPath(self, bm, co, index, vel, co_find): + nVel = vel.normalized() + lVel = vel.length + next = index + + edges = bm.verts[next].link_edges + + bestScore = -2 # scores in range -1 -> 1 (worst to best) + nextVert = None + + for e in edges: + otherVert = e.verts[0] if e.verts[0].index != next else e.verts[1] + score = (otherVert.co - bm.verts[next].co).normalized().dot(nVel) + if score > bestScore: + bestScore = score + nextVert = otherVert + + if nextVert is None: + raise Exception("Invalid mesh") + + ab = nextVert.co - co + ap = co_find - co + + fac = ap.dot(ab) / ab.dot(ab) + adjust = fac * ab + lVel += ab.length * fac + start = co + adjust + + # context.scene.objects["Empty.005"].location = start + + next = nextVert.index + + while True: + currentVert = bm.verts[index].co + nextVert = bm.verts[next].co + + length = (nextVert - currentVert).length + if lVel < length: + fac = lVel/length + target = currentVert * (1 - fac) + nextVert * fac + # context.scene.objects["Empty.003"].location = target + return target - start + lVel -= length + + edges = bm.verts[next].link_edges + bestScore = -2 # scores in range -1 -> 1 (worst to best) + nextVert = None + + endOfPath = True + for e in edges: + if e.verts[0].index != index and e.verts[1].index != index: + endOfPath = False + otherVert = e.verts[0] if e.verts[0].index != next else e.verts[1] + score = (otherVert.co - bm.verts[next].co).normalized().dot(nVel) + if score > bestScore: + bestScore = score + nextVert = otherVert + if endOfPath: + return bm.verts[next].co - start + + index = next + next = nextVert.index + + + def calcRelativeTarget(self, pathObject, lookahead): + context = bpy.context + + kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject) + + obj = context.scene.objects[pathObject] + vel = self.sim.agents[self.userid].globalVelocity + vel = vel * rotation + co_find = pathMatrixInverse * context.scene.objects[self.userid].location + co, index, dist = kd.find(co_find) + offset = self.followPath(bm, co, index, vel, co_find) + + x, y, z = obj.rotation_euler + + if x != 0.0 or y != 0.0 or z != 0.0: + z = mathutils.Matrix.Rotation(-obj.rotation_euler[2], 4, 'Z') + y = mathutils.Matrix.Rotation(-obj.rotation_euler[1], 4, 'Y') + x = mathutils.Matrix.Rotation(-obj.rotation_euler[0], 4, 'X') + + rotation = x * y * z + offset = offset * rotation + + if x != 0.0 or y != 0.0 or z != 0.0: + z = Rotation(context.scene.objects[self.userid].rotation_euler[2], 4, 'Z') + y = Rotation(context.scene.objects[self.userid].rotation_euler[1], 4, 'Y') + x = Rotation(context.scene.objects[self.userid].rotation_euler[0], 4, 'X') + + rotation = x * y * z + return offset * rotation + + return offset + + def rz(self, pathObject, lookahead=5): + target = None + if self.userid in self.resultsCache: + target = self.resultsCache[self.userid] + target = self.calcRelativeTarget(pathObject, lookahead) + + ag = bpy.context.scene.objects[self.userid] + + z = mathutils.Matrix.Rotation(ag.rotation_euler[2], 4, 'Z') + y = mathutils.Matrix.Rotation(ag.rotation_euler[1], 4, 'Y') + x = mathutils.Matrix.Rotation(ag.rotation_euler[0], 4, 'X') + + rotation = x * y * z + relative = target * rotation + + return math.atan2(relative[0], relative[1])/math.pi + + def rx(self, pathObject, lookahead=5): + target = None + if self.userid in self.resultsCache: + target = self.resultsCache[self.userid] + target = self.calcRelativeTarget(pathObject, lookahead) + + ag = bpy.context.scene.objects[self.userid] + + z = mathutils.Matrix.Rotation(ag.rotation_euler[2], 4, 'Z') + y = mathutils.Matrix.Rotation(ag.rotation_euler[1], 4, 'Y') + x = mathutils.Matrix.Rotation(ag.rotation_euler[0], 4, 'X') + + rotation = x * y * z + relative = target * rotation + + return math.atan2(relative[2], relative[1])/math.pi diff --git a/cm_simulate.py b/cm_simulate.py index 84b2e36..110e2bf 100644 --- a/cm_simulate.py +++ b/cm_simulate.py @@ -24,13 +24,15 @@ def __init__(self): Crowd = chan.Crowd(self) Ground = chan.Ground(self) Formation = chan.Formation(self) + Path = chan.Path(self) self.lvars = {"Noise": wr(Noise), "Sound": wr(Sound), "State": wr(State), "World": wr(World), "Crowd": wr(Crowd), "Ground": wr(Ground), - "Formation": wr(Formation)} + "Formation": wr(Formation), + "Path": wr(Path)} if debugMode: self.totalTime = 0 self.totalFrames = 0 From b13ee7afdd7b29587fd161ce237a39f5f0406087 Mon Sep 17 00:00:00 2001 From: Peter Noble Date: Tue, 6 Sep 2016 17:49:54 +0100 Subject: [PATCH 2/4] Constant path radius --- cm_channels/cm_pathChannels.py | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/cm_channels/cm_pathChannels.py b/cm_channels/cm_pathChannels.py index 652357c..a02fa7e 100644 --- a/cm_channels/cm_pathChannels.py +++ b/cm_channels/cm_pathChannels.py @@ -50,7 +50,7 @@ def calcPathData(self, pathObject): return kd, bm, pathMatrixInverse, rotation - def followPath(self, bm, co, index, vel, co_find): + def followPath(self, bm, co, index, vel, co_find, radius): nVel = vel.normalized() lVel = vel.length next = index @@ -78,6 +78,8 @@ def followPath(self, bm, co, index, vel, co_find): lVel += ab.length * fac start = co + adjust + startDistFac = (co_find - start).length / radius + # context.scene.objects["Empty.005"].location = start next = nextVert.index @@ -91,6 +93,12 @@ def followPath(self, bm, co, index, vel, co_find): fac = lVel/length target = currentVert * (1 - fac) + nextVert * fac # context.scene.objects["Empty.003"].location = target + rCorrect = start - co_find + offTargetDist = rCorrect.length - radius + if offTargetDist > 0: + rCorrect *= (offTargetDist / rCorrect.length) + return target - start + rCorrect + # TODO For variable radius add here AND BELOW!!!!!!!!!! return target - start lVel -= length @@ -108,13 +116,19 @@ def followPath(self, bm, co, index, vel, co_find): bestScore = score nextVert = otherVert if endOfPath: - return bm.verts[next].co - start + rCorrect = start - co_find + offTargetDist = rCorrect.length - radius + if offTargetDist > 0: + rCorrect *= (offTargetDist / rCorrect.length) + return target - start + rCorrect + # Also add here for variable length path + return target - start index = next next = nextVert.index - def calcRelativeTarget(self, pathObject, lookahead): + def calcRelativeTarget(self, pathObject, radius, lookahead): context = bpy.context kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject) @@ -124,14 +138,14 @@ def calcRelativeTarget(self, pathObject, lookahead): vel = vel * rotation co_find = pathMatrixInverse * context.scene.objects[self.userid].location co, index, dist = kd.find(co_find) - offset = self.followPath(bm, co, index, vel, co_find) + offset = self.followPath(bm, co, index, vel, co_find, radius) x, y, z = obj.rotation_euler if x != 0.0 or y != 0.0 or z != 0.0: - z = mathutils.Matrix.Rotation(-obj.rotation_euler[2], 4, 'Z') - y = mathutils.Matrix.Rotation(-obj.rotation_euler[1], 4, 'Y') - x = mathutils.Matrix.Rotation(-obj.rotation_euler[0], 4, 'X') + z = Rotation(-obj.rotation_euler[2], 4, 'Z') + y = Rotation(-obj.rotation_euler[1], 4, 'Y') + x = Rotation(-obj.rotation_euler[0], 4, 'X') rotation = x * y * z offset = offset * rotation @@ -146,11 +160,11 @@ def calcRelativeTarget(self, pathObject, lookahead): return offset - def rz(self, pathObject, lookahead=5): + def rz(self, pathObject, radius, lookahead=5): target = None if self.userid in self.resultsCache: target = self.resultsCache[self.userid] - target = self.calcRelativeTarget(pathObject, lookahead) + target = self.calcRelativeTarget(pathObject, radius, lookahead) ag = bpy.context.scene.objects[self.userid] @@ -163,11 +177,11 @@ def rz(self, pathObject, lookahead=5): return math.atan2(relative[0], relative[1])/math.pi - def rx(self, pathObject, lookahead=5): + def rx(self, pathObject, radius, lookahead=5): target = None if self.userid in self.resultsCache: target = self.resultsCache[self.userid] - target = self.calcRelativeTarget(pathObject, lookahead) + target = self.calcRelativeTarget(pathObject, radius, lookahead) ag = bpy.context.scene.objects[self.userid] From 3551bf39d9c4c5e37344456191563619c3f49291 Mon Sep 17 00:00:00 2001 From: Peter Noble Date: Wed, 7 Sep 2016 18:47:47 +0100 Subject: [PATCH 3/4] Fix 3D path following --- cm_channels/cm_pathChannels.py | 65 +++++++++------------------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/cm_channels/cm_pathChannels.py b/cm_channels/cm_pathChannels.py index a02fa7e..0a68dbe 100644 --- a/cm_channels/cm_pathChannels.py +++ b/cm_channels/cm_pathChannels.py @@ -2,6 +2,7 @@ import mathutils from mathutils import Vector Rotation = mathutils.Matrix.Rotation +Euler = mathutils.Euler import math import bmesh @@ -80,19 +81,16 @@ def followPath(self, bm, co, index, vel, co_find, radius): startDistFac = (co_find - start).length / radius - # context.scene.objects["Empty.005"].location = start - - next = nextVert.index + nextIndex = nextVert.index while True: currentVert = bm.verts[index].co - nextVert = bm.verts[next].co + nextVert = bm.verts[nextIndex].co length = (nextVert - currentVert).length if lVel < length: fac = lVel/length target = currentVert * (1 - fac) + nextVert * fac - # context.scene.objects["Empty.003"].location = target rCorrect = start - co_find offTargetDist = rCorrect.length - radius if offTargetDist > 0: @@ -104,28 +102,30 @@ def followPath(self, bm, co, index, vel, co_find, radius): edges = bm.verts[next].link_edges bestScore = -2 # scores in range -1 -> 1 (worst to best) + nextCo = nextVert nextVert = None endOfPath = True for e in edges: if e.verts[0].index != index and e.verts[1].index != index: endOfPath = False - otherVert = e.verts[0] if e.verts[0].index != next else e.verts[1] - score = (otherVert.co - bm.verts[next].co).normalized().dot(nVel) + otherVert = e.verts[0] if e.verts[0].index != nextIndex else e.verts[1] + score = (otherVert.co - bm.verts[nextIndex].co).normalized().dot(nVel) if score > bestScore: bestScore = score nextVert = otherVert if endOfPath: rCorrect = start - co_find offTargetDist = rCorrect.length - radius + target = nextCo if offTargetDist > 0: rCorrect *= (offTargetDist / rCorrect.length) return target - start + rCorrect # Also add here for variable length path return target - start - index = next - next = nextVert.index + index = nextIndex + nextIndex = nextVert.index def calcRelativeTarget(self, pathObject, radius, lookahead): @@ -134,62 +134,31 @@ def calcRelativeTarget(self, pathObject, radius, lookahead): kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject) obj = context.scene.objects[pathObject] - vel = self.sim.agents[self.userid].globalVelocity + vel = self.sim.agents[self.userid].globalVelocity * lookahead vel = vel * rotation co_find = pathMatrixInverse * context.scene.objects[self.userid].location co, index, dist = kd.find(co_find) offset = self.followPath(bm, co, index, vel, co_find, radius) - x, y, z = obj.rotation_euler - - if x != 0.0 or y != 0.0 or z != 0.0: - z = Rotation(-obj.rotation_euler[2], 4, 'Z') - y = Rotation(-obj.rotation_euler[1], 4, 'Y') - x = Rotation(-obj.rotation_euler[0], 4, 'X') - - rotation = x * y * z - offset = offset * rotation + offset = offset * pathMatrixInverse - if x != 0.0 or y != 0.0 or z != 0.0: - z = Rotation(context.scene.objects[self.userid].rotation_euler[2], 4, 'Z') - y = Rotation(context.scene.objects[self.userid].rotation_euler[1], 4, 'Y') - x = Rotation(context.scene.objects[self.userid].rotation_euler[0], 4, 'X') - - rotation = x * y * z - return offset * rotation + eul = Euler([-x for x in context.scene.objects[self.userid].rotation_euler], 'ZYX') + offset.rotate(eul) return offset - def rz(self, pathObject, radius, lookahead=5): + def rz(self, pathObject, radius, lookahead=25): target = None if self.userid in self.resultsCache: target = self.resultsCache[self.userid] target = self.calcRelativeTarget(pathObject, radius, lookahead) - ag = bpy.context.scene.objects[self.userid] - - z = mathutils.Matrix.Rotation(ag.rotation_euler[2], 4, 'Z') - y = mathutils.Matrix.Rotation(ag.rotation_euler[1], 4, 'Y') - x = mathutils.Matrix.Rotation(ag.rotation_euler[0], 4, 'X') + return math.atan2(target[0], target[1])/math.pi - rotation = x * y * z - relative = target * rotation - - return math.atan2(relative[0], relative[1])/math.pi - - def rx(self, pathObject, radius, lookahead=5): + def rx(self, pathObject, radius, lookahead=25): target = None if self.userid in self.resultsCache: target = self.resultsCache[self.userid] target = self.calcRelativeTarget(pathObject, radius, lookahead) - ag = bpy.context.scene.objects[self.userid] - - z = mathutils.Matrix.Rotation(ag.rotation_euler[2], 4, 'Z') - y = mathutils.Matrix.Rotation(ag.rotation_euler[1], 4, 'Y') - x = mathutils.Matrix.Rotation(ag.rotation_euler[0], 4, 'X') - - rotation = x * y * z - relative = target * rotation - - return math.atan2(relative[2], relative[1])/math.pi + return math.atan2(target[2], target[1])/math.pi From 745cb84f0999db60009f25fe5699c32b201dd5ab Mon Sep 17 00:00:00 2001 From: Peter Noble Date: Thu, 8 Sep 2016 16:12:23 +0100 Subject: [PATCH 4/4] Path UI panel --- __init__.py | 22 +++-- cm_channels/__init__.py | 8 ++ cm_channels/cm_pathChannels.py | 145 ++++++++++++++++++++++++++++++--- 3 files changed, 154 insertions(+), 21 deletions(-) diff --git a/__init__.py b/__init__.py index c09e1fe..f61922b 100644 --- a/__init__.py +++ b/__init__.py @@ -227,24 +227,24 @@ def draw(self, context): row.operator(SCENE_OT_cm_stop.bl_idname, icon_value=cicon('stop_sim')) else: row.operator(SCENE_OT_cm_stop.bl_idname, icon='CANCEL') - + row = layout.row() row.separator() - + row = layout.row() if context.scene.show_utilities == False: row.prop(context.scene, "show_utilities", icon="RIGHTARROW", text="Utilities") else: row.prop(context.scene, "show_utilities", icon="TRIA_DOWN", text="Utilities") - + box = layout.box() - + """row = box.row() if preferences.use_custom_icons == True: row.operator("scene.cm_setup_sample_nodes", icon_value=cicon('instant_setup')) else: row.operator("scene.cm_setup_sample_nodes") - + row = box.row() row.separator()""" @@ -274,7 +274,7 @@ def draw(self, context): layout = self.layout scene = context.scene preferences = context.user_preferences.addons[__package__].preferences - + row = layout.row() row.label("Group name") row.label("Number | origin") @@ -289,7 +289,7 @@ def draw(self, context): layout.prop(scene, "cm_view_details", icon='RIGHTARROW') else: layout.prop(scene, "cm_view_details", icon='TRIA_DOWN') - + box = layout.box() index = scene.cm_groups_index @@ -367,7 +367,7 @@ def register(): global cm_generation from . import cm_generation cm_generation.register() - + global cm_utilities from . import cm_utilities cm_utilities.register() @@ -375,6 +375,10 @@ def register(): action_register() event_register() + global cm_channels + from . import cm_channels + cm_channels.register() + def initialise(): sce = bpy.context.scene @@ -395,5 +399,7 @@ def unregister(): cm_generation.unregister() cm_utilities.unregister() + cm_channels.unregister() + if __name__ == "__main__": register() diff --git a/cm_channels/__init__.py b/cm_channels/__init__.py index e6dc31f..378a843 100644 --- a/cm_channels/__init__.py +++ b/cm_channels/__init__.py @@ -6,5 +6,13 @@ from .cm_groundChannels import Ground from .cm_formationChannels import Formation from .cm_pathChannels import Path +from . import cm_pathChannels +Path = cm_pathChannels.Path from .cm_masterChannels import Wrapper + +def register(): + cm_pathChannels.register() + +def unregister(): + cm_pathChannels.unregister() diff --git a/cm_channels/cm_pathChannels.py b/cm_channels/cm_pathChannels.py index 0a68dbe..26fd56f 100644 --- a/cm_channels/cm_pathChannels.py +++ b/cm_channels/cm_pathChannels.py @@ -6,6 +6,11 @@ import math import bmesh +from bpy.props import IntProperty, EnumProperty, CollectionProperty +from bpy.props import PointerProperty, BoolProperty, StringProperty +from bpy.props import FloatProperty +from bpy.types import PropertyGroup, UIList, Panel, Operator + from .cm_masterChannels import MasterChannel as Mc class Path(Mc): @@ -18,6 +23,9 @@ def __init__(self, sim): def newframe(self): self.pathObjectCache = {} + + def setuser(self, userid): + Mc.setuser(self, userid) self.resultsCache = {} def calcPathData(self, pathObject): @@ -131,7 +139,10 @@ def followPath(self, bm, co, index, vel, co_find, radius): def calcRelativeTarget(self, pathObject, radius, lookahead): context = bpy.context - kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject) + if pathObject in self.pathObjectCache: + kd, bm, pathMatrixInverse, rotation = self.pathObjectCache[pathObject] + else: + kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject) obj = context.scene.objects[pathObject] vel = self.sim.agents[self.userid].globalVelocity * lookahead @@ -147,18 +158,126 @@ def calcRelativeTarget(self, pathObject, radius, lookahead): return offset - def rz(self, pathObject, radius, lookahead=25): - target = None - if self.userid in self.resultsCache: - target = self.resultsCache[self.userid] - target = self.calcRelativeTarget(pathObject, radius, lookahead) - + def rz(self, pathName): + if pathName in self.resultsCache: + target = self.resultsCache[pathName] + else: + lookahead = 25 # Hard coded for simplicity + pathEntry = bpy.context.scene.cm_paths.coll.get(pathName) + pathObject = pathEntry.objectName + radius = pathEntry.radius + target = self.calcRelativeTarget(pathObject, radius, lookahead) + self.resultsCache[pathObject] = target return math.atan2(target[0], target[1])/math.pi - def rx(self, pathObject, radius, lookahead=25): - target = None - if self.userid in self.resultsCache: - target = self.resultsCache[self.userid] - target = self.calcRelativeTarget(pathObject, radius, lookahead) - + def rx(self, pathName): + if pathName in self.resultsCache: + target = self.resultsCache[pathName] + else: + lookahead = 25 # Hard coded for simplicity + pathEntry = bpy.context.scene.cm_paths.coll.get(pathName) + pathObject = pathEntry.objectName + radius = pathEntry.radius + target = self.calcRelativeTarget(pathObject, radius, lookahead) + self.resultsCache[pathObject] = target return math.atan2(target[2], target[1])/math.pi + + + +class path_entry(PropertyGroup): + """For storing a single path""" + # name - aliase given to the path + objectName = StringProperty() + radius = FloatProperty(min=0) + + +class paths_collection(PropertyGroup): + coll = CollectionProperty(type=path_entry) + index = IntProperty() + + +class SCENE_OT_cm_path_populate(Operator): + bl_idname = "scene.cm_paths_populate" + bl_label = "Add a path" + + def execute(self, context): + item = context.scene.cm_paths.coll.add() + return {'FINISHED'} + + +class SCENE_OT_cm_path_remove(Operator): + bl_idname = "scene.cm_paths_remove" + bl_label = "Remove a path" + + @classmethod + def poll(cls, context): + s = context.scene + return len(s.cm_paths.coll) > s.cm_paths.index >= 0 + + def execute(self, context): + s = context.scene + s.cm_paths.coll.remove(s.cm_paths.index) + if s.cm_paths.index > 0: + s.cm_paths.index -= 1 + return {'FINISHED'} + + +class SCENE_UL_cm_path(UIList): + """for drawing each row""" + def draw_item(self, context, layout, data, item, icon, active_data, + active_propname): + layout.prop(item, "name", text="") + layout.prop_search(item, "objectName", bpy.data, "objects", text="") + layout.prop(item, "radius") + + +class SCENE_PT_path(Panel): + """Creates CrowdMaster Panel in the node editor""" + bl_label = "Paths" + bl_idname = "SCENE_PT_path" + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'TOOLS' + bl_category = "CrowdMaster" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(self, context): + try: + return bpy.context.space_data.tree_type == 'CrowdMasterTreeType', context.space_data.tree_type == 'CrowdMasterGenTreeType' + except (AttributeError, KeyError, TypeError): + return False + + def draw(self, context): + layout = self.layout + sce = context.scene + + row = layout.row() + + row.template_list("SCENE_UL_cm_path", "", sce.cm_paths, + "coll", sce.cm_paths, "index") + + col = row.column() + sub = col.column(True) + blid_ap = SCENE_OT_cm_path_populate.bl_idname + sub.operator(blid_ap, text="", icon="ZOOMIN") + blid_ar = SCENE_OT_cm_path_remove.bl_idname + sub.operator(blid_ar, text="", icon="ZOOMOUT") + + +def register(): + bpy.utils.register_class(path_entry) + bpy.utils.register_class(paths_collection) + bpy.utils.register_class(SCENE_OT_cm_path_populate) + bpy.utils.register_class(SCENE_OT_cm_path_remove) + bpy.utils.register_class(SCENE_UL_cm_path) + bpy.utils.register_class(SCENE_PT_path) + bpy.types.Scene.cm_paths = PointerProperty(type=paths_collection) + +def unregister(): + bpy.utils.unregister_class(path_entry) + bpy.utils.unregister_class(paths_collection) + bpy.utils.unregister_class(SCENE_OT_cm_path_populate) + bpy.utils.unregister_class(SCENE_OT_cm_path_remove) + bpy.utils.unregister_class(SCENE_UL_cm_path) + bpy.utils.unregister_class(SCENE_PT_path) + del bpy.types.Scene.cm_paths