diff --git a/__init__.py b/__init__.py index e885e58..b0db446 100644 --- a/__init__.py +++ b/__init__.py @@ -18,7 +18,7 @@ bl_info = { "name": "Topologic", "author": "Wassim Jabi", - "version": "0.8.2.5", + "version": "0.8.2.6", "blender": (3, 2, 0), "location": "Node Editor", "category": "Node", @@ -76,6 +76,7 @@ def nodes_index(): ("Topologic.VertexCoordinates", "SvVertexCoordinates"), ("Topologic.VertexDistance", "SvVertexDistance"), ("Topologic.VertexEnclosingCell", "SvVertexEnclosingCell"), + ("Topologic.VertexNearestTopology", "SvVertexNearestTopology"), ("Topologic.VertexNearestVertex", "SvVertexNearestVertex"), ("Topologic.VertexProject", "SvVertexProject"), ("Topologic.EdgeByStartVertexEndVertex", "SvEdgeByStartVertexEndVertex"), @@ -131,6 +132,7 @@ def nodes_index(): ("Topologic.ShellHyperbolicParaboloid", "SvShellHyperbolicParaboloid"), ("Topologic.ShellInternalBoundaries", "SvShellInternalBoundaries"), ("Topologic.ShellIsClosed", "SvShellIsClosed"), + ("Topologic.ShellPie", "SvShellPie"), ("Topologic.ShellTessellatedDisk", "SvShellTessellatedDisk"), ("Topologic.CellCone", "SvCellCone"), ("Topologic.CellCylinder", "SvCellCylinder"), @@ -171,7 +173,6 @@ def nodes_index(): ("Topologic.TopologyApertures", "SvTopologyApertures"), ("Topologic.TopologyBlenderGeometry", "SvTopologyBlenderGeometry"), ("Topologic.TopologyBoolean", "SvTopologyBoolean"), - ("Topologic.TopologyBoundingBox", "SvTopologyBoundingBox"), ("Topologic.TopologyByGeometry", "SvTopologyByGeometry"), ("Topologic.TopologyByImportedBRep", "SvTopologyByImportedBRep"), ("Topologic.TopologyByImportedJSONMK1", "SvTopologyByImportedJSONMK1"), @@ -365,6 +366,7 @@ def nodes_index(): ifchoneybeeNodes = [("Topologic.IFCExportToHBJSON", "SvIFCExportToHBJSON")] osifcNodes = [("Topologic.EnergyModelByImportedIFC", "SvEnergyModelByImportedIFC")] osifc = 0 + pyobbNodes = [("Topologic.TopologyBoundingBox", "SvTopologyBoundingBox")] try: import numpy @@ -472,6 +474,12 @@ def nodes_index(): coreNodes = coreNodes+pandasNodes except: print("Topologic - Warning: Could not import pandas so Topologic.GraphExportToCSV is not available.") + try: + import numpy + import pyobb + coreNodes = coreNodes+pyobbNodes + except: + print("Topologic - Warning: Could not import numpy and/or pyobb so some related nodes are not available.") return [("Topologic", coreNodes)] def make_node_list(): @@ -559,6 +567,7 @@ def draw(self, context): ['SvVertexCoordinates'], ['SvVertexDistance'], ['SvVertexEnclosingCell'], + ['SvVertexNearestTopology'], ['SvVertexNearestVertex'], ['SvVertexProject'], ]) @@ -661,6 +670,7 @@ def draw(self, context): ['SvShellHyperbolicParaboloid'], ['SvShellInternalBoundaries'], ['SvShellIsClosed'], + ['SvShellPie'], ['SvShellTessellatedDisk'], ]) diff --git a/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend b/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend index d599689..35c409e 100644 Binary files a/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend and b/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend differ diff --git a/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend1 b/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend1 index 53b37a9..d599689 100644 Binary files a/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend1 and b/examples/TopologicEnergy/TopologicBuildingWithDictionaries.blend1 differ diff --git a/nodes/Topologic/FaceTrimByWire.py b/nodes/Topologic/FaceTrimByWire.py index 30ede97..dd1c83e 100644 --- a/nodes/Topologic/FaceTrimByWire.py +++ b/nodes/Topologic/FaceTrimByWire.py @@ -92,8 +92,11 @@ def transposeList(l): def processItem(item): face = item[0] wire = item[1] - reverseWire = item[2] - return topologic.FaceUtility.TrimByWire(face, wire, reverseWire) + reverse = item[2] + trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False) + if reverse: + trimmed_face = face.Difference(trimmed_face) + return trimmed_face replication = [("Default", "Default", "", 1),("Trim", "Trim", "", 2),("Iterate", "Iterate", "", 3),("Repeat", "Repeat", "", 4),("Interlace", "Interlace", "", 5)] @@ -104,13 +107,13 @@ class SvFaceTrimByWire(bpy.types.Node, SverchCustomTreeNode): """ bl_idname = 'SvFaceTrimByWire' bl_label = 'Face.TrimByWire' - ReverseWire: BoolProperty(name="Reverse Wire", default=False, update=updateNode) + Reverse: BoolProperty(name="Reverse", default=False, update=updateNode) Replication: EnumProperty(name="Replication", description="Replication", default="Default", items=replication, update=updateNode) def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Face') self.inputs.new('SvStringsSocket', 'Wire') - self.inputs.new('SvStringsSocket', 'Reverse Wire').prop_name = 'ReverseWire' + self.inputs.new('SvStringsSocket', 'Reverse').prop_name = 'Reverse' self.outputs.new('SvStringsSocket', 'Face') def draw_buttons(self, context, layout): @@ -123,9 +126,9 @@ def process(self): faceList = flatten(faceList) wireList = self.inputs['Wire'].sv_get(deepcopy=True) wireList = flatten(wireList) - reverseWireList = self.inputs['Reverse Wire'].sv_get(deepcopy=True) - reverseWireList = flatten(reverseWireList) - inputs = [faceList, wireList, reverseWireList] + reverseList = self.inputs['Reverse'].sv_get(deepcopy=True) + reverseList = flatten(reverseList) + inputs = [faceList, wireList, reverseList] outputs = [] if ((self.Replication) == "Default"): inputs = repeat(inputs) diff --git a/nodes/Topologic/InstallDependencies.py b/nodes/Topologic/InstallDependencies.py index 36a334c..5295255 100644 --- a/nodes/Topologic/InstallDependencies.py +++ b/nodes/Topologic/InstallDependencies.py @@ -23,7 +23,8 @@ 'sklearn', 'dgl', 'plotly', - 'specklepy'] + 'specklepy', + 'pyobb'] def install_dependency(module): # upgrade pip diff --git a/nodes/Topologic/ShellPie.py b/nodes/Topologic/ShellPie.py new file mode 100644 index 0000000..2e5123b --- /dev/null +++ b/nodes/Topologic/ShellPie.py @@ -0,0 +1,252 @@ +# * This file is part of Topologic software library. +# * Copyright(C) 2021, Cardiff University and University College London +# * +# * This program is free software: you can redistribute it and/or modify +# * it under the terms of the GNU Affero General Public License as published by +# * the Free Software Foundation, either version 3 of the License, or +# * (at your option) any later version. +# * +# * This program is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# * GNU Affero General Public License for more details. +# * +# * You should have received a copy of the GNU Affero General Public License +# * along with this program. If not, see . + +import bpy +from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + +import topologic +from topologic import Vertex, Edge, Wire, Face, Shell, Cell, CellComplex, Cluster, Topology +import math +from . import Replication + +def wireByVertices(vList): + edges = [] + for i in range(len(vList)-1): + edges.append(topologic.Edge.ByStartVertexEndVertex(vList[i], vList[i+1])) + edges.append(topologic.Edge.ByStartVertexEndVertex(vList[-1], vList[0])) + return topologic.Wire.ByEdges(edges) + +def processItem(item): + origin, \ + radiusA, \ + radiusB, \ + sides, \ + rings, \ + fromAngle, \ + toAngle, \ + dirX, \ + dirY, \ + dirZ, \ + tolerance, \ + placement = item + if not origin: + origin = Vertex.ByCoordinates(0,0,0) + if not isinstance(origin, topologic.Vertex): + return None + if toAngle < fromAngle: + toAngle += 360 + if abs(toAngle-fromAngle) < tolerance: + return None + fromAngle = math.radians(fromAngle) + toAngle = math.radians(toAngle) + angleRange = toAngle - fromAngle + radiusA = abs(radiusA) + radiusB = abs(radiusB) + if radiusB > radiusA: + temp = radiusA + radiusA = radiusB + radiusB = temp + if abs(radiusA - radiusB) < tolerance or radiusA < tolerance: + return None + radiusRange = radiusA - radiusB + print("Radius Range", radiusRange) + sides = int(abs(math.floor(sides))) + if sides < 3: + return None + rings = int(abs(rings)) + if radiusB < tolerance: + radiusB = 0 + xOffset = 0 + yOffset = 0 + zOffset = 0 + if placement.lower() == "lowerleft": + xOffset = radiusA + yOffset = radiusA + uOffset = float(angleRange)/float(sides) + vOffset = float(radiusRange)/float(rings) + faces = [] + if radiusB > tolerance: + for i in range(rings): + r1 = radiusA - vOffset*i + r2 = radiusA - vOffset*(i+1) + for j in range(sides): + a1 = fromAngle + uOffset*j + a2 = fromAngle + uOffset*(j+1) + x1 = math.sin(a1)*r1 + y1 = math.cos(a1)*r1 + z1 = 0 + x2 = math.sin(a1)*r2 + y2 = math.cos(a1)*r2 + z2 = 0 + x3 = math.sin(a2)*r2 + y3 = math.cos(a2)*r2 + z3 = 0 + x4 = math.sin(a2)*r1 + y4 = math.cos(a2)*r1 + z4 = 0 + v1 = Vertex.ByCoordinates(x1,y1,z1) + v2 = Vertex.ByCoordinates(x2,y2,z2) + v3 = Vertex.ByCoordinates(x3,y3,z3) + v4 = Vertex.ByCoordinates(x4,y4,z4) + f1 = Face.ByExternalBoundary(wireByVertices([v1,v2,v3,v4])) + faces.append(f1) + else: + x1 = 0 + y1 = 0 + z1 = 0 + v1 = Vertex.ByCoordinates(x1,y1,z1) + for j in range(sides): + a1 = fromAngle + uOffset*j + a2 = fromAngle + uOffset*(j+1) + x2 = math.sin(a1)*radiusA + y2 = math.cos(a1)*radiusA + z2 = 0 + x3 = math.sin(a2)*radiusA + y3 = math.cos(a2)*radiusA + z3 = 0 + v2 = Vertex.ByCoordinates(x2,y2,z2) + v3 = Vertex.ByCoordinates(x3,y3,z3) + f1 = Face.ByExternalBoundary(wireByVertices([v2,v1,v3])) + faces.append(f1) + + shell = Shell.ByFaces(faces, tolerance) + if not shell: + return None + x1 = 0 + y1 = 0 + z1 = 0 + x2 = 0 + dirX + y2 = 0 + dirY + z2 = 0 + dirZ + dx = x2 - x1 + dy = y2 - y1 + dz = z2 - z1 + dist = math.sqrt(dx**2 + dy**2 + dz**2) + phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis + if dist < tolerance: + theta = 0 + else: + theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis + shell = topologic.TopologyUtility.Rotate(shell, origin, 0, 1, 0, theta) + shell = topologic.TopologyUtility.Rotate(shell, origin, 0, 0, 1, phi) + shell = topologic.TopologyUtility.Translate(shell, origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset) + return shell + +placements = [("Center", "Center", "", 1),("LowerLeft", "LowerLeft", "", 2)] +replication = [("Trim", "Trim", "", 1),("Iterate", "Iterate", "", 2),("Repeat", "Repeat", "", 3),("Interlace", "Interlace", "", 4)] + +class SvShellPie(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Topologic + Tooltip: Creates a Pie Shape (Shell) from the input parameters + """ + bl_idname = 'SvShellPie' + bl_label = 'Shell.Pie' + bl_icon = 'SELECT_DIFFERENCE' + + RadiusA: FloatProperty(name="Radius A", default=0.5, min=0.0001, precision=4, update=updateNode) + RadiusB: FloatProperty(name="Radius B", default=0, min=0, precision=4, update=updateNode) + Sides: IntProperty(name="Sides", default=32, min=3, update=updateNode) + Rings: IntProperty(name="Rings", default=0, min=0, update=updateNode) + FromAngle: FloatProperty(name="From Angle", default=0, min=0, max=360, precision=4, update=updateNode) + ToAngle: FloatProperty(name="To Angle", default=360, min=0, max=360, precision=4, update=updateNode) + DirX: FloatProperty(name="Dir X", default=0, precision=4, update=updateNode) + DirY: FloatProperty(name="Dir Y", default=0, precision=4, update=updateNode) + DirZ: FloatProperty(name="Dir Z", default=1, precision=4, update=updateNode) + Tolerance: FloatProperty(name="Tolerance", default=0.001, precision=4, update=updateNode) + Placement: EnumProperty(name="Placement", description="Specify origin placement", default="Center", items=placements, update=updateNode) + Replication: EnumProperty(name="Replication", description="Replication", default="Iterate", items=replication, update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'Origin') + self.inputs.new('SvStringsSocket', 'Radius A').prop_name = 'RadiusA' + self.inputs.new('SvStringsSocket', 'Radius B').prop_name = 'RadiusB' + self.inputs.new('SvStringsSocket', 'Sides').prop_name = 'Sides' + self.inputs.new('SvStringsSocket', 'Rings').prop_name = 'Rings' + self.inputs.new('SvStringsSocket', 'From').prop_name = 'FromAngle' + self.inputs.new('SvStringsSocket', 'To').prop_name = 'ToAngle' + self.inputs.new('SvStringsSocket', 'Dir X').prop_name = 'DirX' + self.inputs.new('SvStringsSocket', 'Dir Y').prop_name = 'DirY' + self.inputs.new('SvStringsSocket', 'Dir Z').prop_name = 'DirZ' + self.inputs.new('SvStringsSocket', 'Tolerance').prop_name = 'Tolerance' + self.outputs.new('SvStringsSocket', 'Shell') + self.width = 175 + for socket in self.inputs: + if socket.prop_name != '': + socket.custom_draw = "draw_sockets" + + def draw_sockets(self, socket, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text=(socket.name or "Untitled") + f". {socket.objects_number or ''}") + split.row().prop(self, socket.prop_name, text="") + + def draw_buttons(self, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Replication") + split.row().prop(self, "Replication",text="") + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Placement") + split.row().prop(self, "Placement",text="") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + inputs_nested = [] + inputs_flat = [] + for anInput in self.inputs: + if anInput.name == 'Origin': + if not (self.inputs['Origin'].is_linked): + inp = [topologic.Vertex.ByCoordinates(0,0,0)] + else: + inp = anInput.sv_get(deepcopy=True) + else: + inp = anInput.sv_get(deepcopy=True) + inputs_nested.append(inp) + inputs_flat.append(Replication.flatten(inp)) + inputs_replicated = Replication.replicateInputs(inputs_flat, self.Replication) + outputs = [] + for anInput in inputs_replicated: + outputs.append(processItem(anInput+[self.Placement])) + inputs_flat = [] + for anInput in self.inputs: + if anInput.name == 'Origin': + if not (self.inputs['Origin'].is_linked): + inp = [topologic.Vertex.ByCoordinates(0,0,0)] + else: + inp = anInput.sv_get(deepcopy=True) + else: + inp = anInput.sv_get(deepcopy=True) + inputs_flat.append(Replication.flatten(inp)) + if self.Replication == "Interlace": + outputs = Replication.re_interlace(outputs, inputs_flat) + else: + match_list = Replication.best_match(inputs_nested, inputs_flat, self.Replication) + outputs = Replication.unflatten(outputs, match_list) + if len(outputs) == 1: + if isinstance(outputs[0], list): + outputs = outputs[0] + self.outputs['Shell'].sv_set(outputs) + +def register(): + bpy.utils.register_class(SvShellPie) + +def unregister(): + bpy.utils.unregister_class(SvShellPie) diff --git a/nodes/Topologic/TopologyAddApertures.py b/nodes/Topologic/TopologyAddApertures.py index 97970d5..99a3e8d 100644 --- a/nodes/Topologic/TopologyAddApertures.py +++ b/nodes/Topologic/TopologyAddApertures.py @@ -4,93 +4,8 @@ from sverchok.data_structure import updateNode import topologic -from topologic import Vertex, Edge, Wire, Face, Shell, Cell, CellComplex, Cluster, Topology -from . import VertexNearestVertex, DictionaryByKeysValues, TopologySetDictionary, DictionaryValueAtKey -import time - -# From https://stackabuse.com/python-how-to-flatten-list-of-lists/ -def flatten(element): - returnList = [] - if isinstance(element, list) == True: - for anItem in element: - returnList = returnList + flatten(anItem) - else: - returnList = [element] - return returnList - -def repeat(list): - maxLength = len(list[0]) - for aSubList in list: - newLength = len(aSubList) - if newLength > maxLength: - maxLength = newLength - for anItem in list: - if (len(anItem) > 0): - itemToAppend = anItem[-1] - else: - itemToAppend = None - for i in range(len(anItem), maxLength): - anItem.append(itemToAppend) - return list - -# From https://stackoverflow.com/questions/34432056/repeat-elements-of-list-between-each-other-until-we-reach-a-certain-length -def onestep(cur,y,base): - # one step of the iteration - if cur is not None: - y.append(cur) - base.append(cur) - else: - y.append(base[0]) # append is simplest, for now - base = base[1:]+[base[0]] # rotate - return base - -def iterate(list): - maxLength = len(list[0]) - returnList = [] - for aSubList in list: - newLength = len(aSubList) - if newLength > maxLength: - maxLength = newLength - for anItem in list: - for i in range(len(anItem), maxLength): - anItem.append(None) - y=[] - base=[] - for cur in anItem: - base = onestep(cur,y,base) - returnList.append(y) - return returnList - -def trim(list): - minLength = len(list[0]) - returnList = [] - for aSubList in list: - newLength = len(aSubList) - if newLength < minLength: - minLength = newLength - for anItem in list: - anItem = anItem[:minLength] - returnList.append(anItem) - return returnList - -# Adapted from https://stackoverflow.com/questions/533905/get-the-cartesian-product-of-a-series-of-lists -def interlace(ar_list): - if not ar_list: - yield [] - else: - for a in ar_list[0]: - for prod in interlace(ar_list[1:]): - yield [a,]+prod - -def transposeList(l): - length = len(l[0]) - returnList = [] - for i in range(length): - tempRow = [] - for j in range(len(l)): - tempRow.append(l[j][i]) - returnList.append(tempRow) - return returnList +from . import Replication +from . import VertexNearestTopology, DictionaryByKeysValues, DictionaryValueAtKey def isInside(aperture, face, tolerance): return (topologic.VertexUtility.Distance(aperture.Topology.Centroid(), face) < tolerance) @@ -130,48 +45,50 @@ def internalVertex(topology, tolerance): return vst def processApertures(subTopologies, apertureCluster, exclusive, tolerance): - apertures = [] - cells = [] - faces = [] - edges = [] - vertices = [] - _ = apertureCluster.Cells(None, cells) - _ = apertureCluster.Faces(None, faces) - _ = apertureCluster.Vertices(None, vertices) - # apertures are assumed to all be of the same topology type. - if len(cells) > 0: - apertures = cells - elif len(faces) > 0: - apertures = faces - elif len(edges) > 0: - apertures = edges - elif len(vertices) > 0: - apertures = vertices - else: - apertures = [] - usedTopologies = [] - temp_verts = [] - for i, subTopology in enumerate(subTopologies): - usedTopologies.append(0) - temp_v = internalVertex(subTopology, tolerance) - d = DictionaryByKeysValues.processItem([["id"], [i]]) - temp_v = TopologySetDictionary.processItem([temp_v, d]) - temp_verts.append(temp_v) - clus = topologic.Cluster.ByTopologies(temp_verts) - tree = VertexNearestVertex.kdtree(clus) - for aperture in apertures: - apCenter = internalVertex(aperture, tolerance) - nearest_vert = VertexNearestVertex.find_nearest_neighbor(tree=tree, vertex=apCenter) - d = nearest_vert.GetDictionary() - i = DictionaryValueAtKey.processItem([d,"id"]) - subTopology = subTopologies[i] - if exclusive == True and usedTopologies[i] == 1: - continue - context = topologic.Context.ByTopologyParameters(subTopology, 0.5, 0.5, 0.5) - _ = topologic.Aperture.ByTopologyContext(aperture, context) - if exclusive == True: - usedTopologies[i] = 1 - return None + cells = [] + faces = [] + edges = [] + vertices = [] + apertures = [] + if not apertureCluster: + return None + _ = apertureCluster.Cells(None, cells) + _ = apertureCluster.Faces(None, faces) + _ = apertureCluster.Edges(None, edges) + _ = apertureCluster.Vertices(None, vertices) + # apertures are assumed to all be of the same topology type. + if len(cells) > 0: + apertures = cells + elif len(faces) > 0: + apertures = faces + elif len(edges) > 0: + apertures = edges + elif len(vertices) > 0: + apertures = vertices + else: + apertures = [] + usedTopologies = [] + topologyType = subTopologies[0].GetTypeAsString() + tempTopologies = [] + for i in range(len(subTopologies)): + usedTopologies.append(0) + d = DictionaryByKeysValues.processItem([["index"], [i]]) + tempTopology = subTopologies[i].DeepCopy() + _ = tempTopology.SetDictionary(d) + tempTopologies.append(tempTopology) + cluster = topologic.Cluster.ByTopologies(tempTopologies) + for aperture in apertures: + apCenter = internalVertex(aperture, tolerance) + nearestTempTopology = VertexNearestTopology.processItem([apCenter, cluster, False, topologyType]) + index = DictionaryValueAtKey.processItem([nearestTempTopology.GetDictionary(), "index"]) + nearestTopology = subTopologies[index] + if exclusive == True and usedTopologies[i] == 1: + continue + context = topologic.Context.ByTopologyParameters(nearestTopology, 0.5, 0.5, 0.5) + _ = topologic.Aperture.ByTopologyContext(aperture, context) + if exclusive == True: + usedTopologies[i] = 1 + return None def processItem(item): topology = item[0].DeepCopy() @@ -192,7 +109,6 @@ def processItem(item): topologyTypes = [("Vertex", "Vertex", "", 1),("Edge", "Edge", "", 2),("Face", "Face", "", 3)] replication = [("Default", "Default", "", 1),("Trim", "Trim", "", 2),("Iterate", "Iterate", "", 3),("Repeat", "Repeat", "", 4),("Interlace", "Interlace", "", 5)] - class SvTopologyAddApertures(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Topologic @@ -200,59 +116,68 @@ class SvTopologyAddApertures(bpy.types.Node, SverchCustomTreeNode): """ bl_idname = 'SvTopologyAddApertures' bl_label = 'Topology.AddApertures' + bl_icon = 'SELECT_DIFFERENCE' Exclusive: BoolProperty(name="Exclusive", default=True, update=updateNode) - ToleranceProp: FloatProperty(name="Tolerance", default=0.0001, precision=4, update=updateNode) subtopologyType: EnumProperty(name="Apply To:", description="Specify subtopology type to apply the apertures to", default="Face", items=topologyTypes, update=updateNode) + ToleranceProp: FloatProperty(name="Tolerance", default=0.0001, precision=4, update=updateNode) Replication: EnumProperty(name="Replication", description="Replication", default="Default", items=replication, update=updateNode) def sv_init(self, context): + self.width = 200 self.inputs.new('SvStringsSocket', 'Topology') self.inputs.new('SvStringsSocket', 'Aperture Cluster') self.inputs.new('SvStringsSocket', 'Exclusive').prop_name = 'Exclusive' self.inputs.new('SvStringsSocket', 'Tolerance').prop_name = 'ToleranceProp' self.outputs.new('SvStringsSocket', 'Topology') + for socket in self.inputs: + if socket.prop_name != '': + socket.custom_draw = "draw_sockets" + + def draw_sockets(self, socket, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text=(socket.name or "Untitled") + f". {socket.objects_number or ''}") + split.row().prop(self, socket.prop_name, text="") def draw_buttons(self, context, layout): - layout.prop(self, "Replication",text="") - layout.prop(self, "subtopologyType",text="") + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Replication") + split.row().prop(self, "Replication",text="") + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Apply To") + split.row().prop(self, "subtopologyType",text="") def process(self): - start = time.time() if not any(socket.is_linked for socket in self.outputs): return if not any(socket.is_linked for socket in self.inputs): + self.outputs['Distance'].sv_set([]) return - topologyList = self.inputs['Topology'].sv_get(deepcopy=True) - aperturesList = self.inputs['Aperture Cluster'].sv_get(deepcopy=True) - exclusiveList = self.inputs['Exclusive'].sv_get(deepcopy=True) - toleranceList = self.inputs['Tolerance'].sv_get(deepcopy=True) - - topologyList = flatten(topologyList) - aperturesList = flatten(aperturesList) - exclusiveList = flatten(exclusiveList) - toleranceList = flatten(toleranceList) - subTopologiesList = [self.subtopologyType] - inputs = [topologyList, aperturesList, exclusiveList, toleranceList, subTopologiesList] - if ((self.Replication) == "Default"): - inputs = iterate(inputs) - inputs = transposeList(inputs) - if ((self.Replication) == "Trim"): - inputs = trim(inputs) - inputs = transposeList(inputs) - elif ((self.Replication) == "Iterate"): - inputs = iterate(inputs) - inputs = transposeList(inputs) - elif ((self.Replication) == "Repeat"): - inputs = repeat(inputs) - inputs = transposeList(inputs) - elif ((self.Replication) == "Interlace"): - inputs = list(interlace(inputs)) + inputs_nested = [] + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_nested.append(inp) + inputs_flat.append(Replication.flatten(inp)) + inputs_nested.append([self.subtopologyType]) + inputs_flat.append([self.subtopologyType]) + inputs_replicated = Replication.replicateInputs(inputs_flat.copy(), self.Replication) outputs = [] - for anInput in inputs: + for anInput in inputs_replicated: outputs.append(processItem(anInput)) + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_flat.append(Replication.flatten(inp)) + inputs_flat.append([self.subtopologyType]) + if self.Replication == "Interlace": + outputs = Replication.re_interlace(outputs, inputs_flat) + else: + match_list = Replication.best_match(inputs_nested, inputs_flat, self.Replication) + outputs = Replication.unflatten(outputs, match_list) self.outputs['Topology'].sv_set(outputs) - end = time.time() - print("Topology.AddApertures Operation consumed "+str(round(end - start,2))+" seconds") def register(): bpy.utils.register_class(SvTopologyAddApertures) diff --git a/nodes/Topologic/TopologyBoundingBox.py b/nodes/Topologic/TopologyBoundingBox.py index 868679e..244424d 100644 --- a/nodes/Topologic/TopologyBoundingBox.py +++ b/nodes/Topologic/TopologyBoundingBox.py @@ -1,11 +1,18 @@ import bpy -from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty +from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty, BoolProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode import topologic from topologic import Vertex, Edge, Wire, Face, Shell, Cell, CellComplex, Cluster, Topology +from . import WireByVertices, TopologyTransform, MatrixByTranslation, MatrixMultiply +from mathutils import Matrix + import math +try: + from pyobb.obb import OBB +except: + raise Exception("No pyobb") def wireByVertices(vList): edges = [] @@ -15,35 +22,67 @@ def wireByVertices(vList): return topologic.Wire.ByEdges(edges) def processItem(item): + topology, optimized = item vertices = [] - _ = item.Vertices(None, vertices) - x = [] - y = [] - z = [] - for aVertex in vertices: - x.append(aVertex.X()) - y.append(aVertex.Y()) - z.append(aVertex.Z()) - minX = min(x) - minY = min(y) - minZ = min(z) - maxX = max(x) - maxY = max(y) - maxZ = max(z) + _ = topology.Vertices(None, vertices) + c = topology.Centroid() + mat = Matrix([[1,0,0,c.X()*-1.0], + [0,1,0,c.Y()*-1.0], + [0,0,1,c.Z()*-1.0], + [0,0,0,1]]) + if optimized: + points = [] + for aVertex in vertices: + points.append((aVertex.X(), aVertex.Y(), aVertex.Z())) + obb = OBB.build_from_points(points) + rotation = obb.rotation.tolist() + centroid = obb.centroid + print(rotation) + print(centroid) + new_rows = [] + for i, row in enumerate(rotation): + row.append(0) + new_rows.append(row) + new_rows.append([0,0,0,1]) + rotation = Matrix(new_rows) + translation = MatrixByTranslation.processItem([centroid[0]*-1.0, centroid[1]*-1.0, centroid[2]*-1.0]) + mat = MatrixMultiply.processItem([rotation, translation]) + print(mat) + obb_vertices = [] + for obb_point in obb.points: + obb_vertices.append(topologic.Vertex.ByCoordinates(obb_point[0], obb_point[1], obb_point[2])) + baseWire = WireByVertices.processItem([topologic.Cluster.ByTopologies(obb_vertices[0:4]),True]) + topWire = WireByVertices.processItem([topologic.Cluster.ByTopologies(obb_vertices[4:]),True]) + + else: + x = [] + y = [] + z = [] + for aVertex in vertices: + x.append(aVertex.X()) + y.append(aVertex.Y()) + z.append(aVertex.Z()) + minX = min(x) + minY = min(y) + minZ = min(z) + maxX = max(x) + maxY = max(y) + maxZ = max(z) - vb1 = topologic.Vertex.ByCoordinates(minX, minY, minZ) - vb2 = topologic.Vertex.ByCoordinates(maxX, minY, minZ) - vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, minZ) - vb4 = topologic.Vertex.ByCoordinates(minX, maxY, minZ) + vb1 = topologic.Vertex.ByCoordinates(minX, minY, minZ) + vb2 = topologic.Vertex.ByCoordinates(maxX, minY, minZ) + vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, minZ) + vb4 = topologic.Vertex.ByCoordinates(minX, maxY, minZ) - vt1 = topologic.Vertex.ByCoordinates(minX, minY, maxZ) - vt2 = topologic.Vertex.ByCoordinates(maxX, minY, maxZ) - vt3 = topologic.Vertex.ByCoordinates(maxX, maxY, maxZ) - vt4 = topologic.Vertex.ByCoordinates(minX, maxY, maxZ) - baseWire = wireByVertices([vb1, vb2, vb3, vb4]) - topWire = wireByVertices([vt1, vt2, vt3, vt4]) + vt1 = topologic.Vertex.ByCoordinates(minX, minY, maxZ) + vt2 = topologic.Vertex.ByCoordinates(maxX, minY, maxZ) + vt3 = topologic.Vertex.ByCoordinates(maxX, maxY, maxZ) + vt4 = topologic.Vertex.ByCoordinates(minX, maxY, maxZ) + baseWire = wireByVertices([vb1, vb2, vb3, vb4]) + topWire = wireByVertices([vt1, vt2, vt3, vt4]) wires = [baseWire, topWire] - return ([topologic.CellUtility.ByLoft(wires), vb1, vt3]) + cell = topologic.CellUtility.ByLoft(wires) + return [cell, mat] def matchLengths(list): @@ -68,29 +107,28 @@ class SvTopologyBoundingBox(bpy.types.Node, SverchCustomTreeNode): """ bl_idname = 'SvTopologyBoundingBox' bl_label = 'Topology.BoundingBox' - + optimized: BoolProperty(name="Optimized", default=False, update=updateNode) + def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Topology') + self.inputs.new('SvStringsSocket', 'Optimized').prop_name = 'optimized' self.outputs.new('SvStringsSocket', 'Cell') - self.outputs.new('SvStringsSocket', 'Min Vertex') - self.outputs.new('SvStringsSocket', 'Max Vertex') - + self.outputs.new('SvMatrixSocket', 'Matrix') def process(self): if not any(socket.is_linked for socket in self.outputs): return - cellOutputs = [] - minVertexOutputs = [] - maxVertexOutputs = [] inputs = self.inputs['Topology'].sv_get(deepcopy=False) + optimized = self.inputs['Optimized'].sv_get(deepcopy=False)[0][0] + cellOutputs = [] + matOutputs = [] + for anInput in inputs: - output = processItem(anInput) + output = processItem([anInput, optimized]) cellOutputs.append(output[0]) - minVertexOutputs.append(output[1]) - maxVertexOutputs.append(output[2]) + matOutputs.append(output[1]) self.outputs['Cell'].sv_set(cellOutputs) - self.outputs['Min Vertex'].sv_set(minVertexOutputs) - self.outputs['Max Vertex'].sv_set(maxVertexOutputs) + self.outputs['Matrix'].sv_set(matOutputs) def register(): bpy.utils.register_class(SvTopologyBoundingBox) diff --git a/nodes/Topologic/TopologyByImportedJSONMK1.py b/nodes/Topologic/TopologyByImportedJSONMK1.py index 9c47861..93a5a68 100644 --- a/nodes/Topologic/TopologyByImportedJSONMK1.py +++ b/nodes/Topologic/TopologyByImportedJSONMK1.py @@ -4,7 +4,7 @@ from sverchok.data_structure import updateNode import topologic -from . import VertexNearestVertex, DictionaryValueAtKey, DictionaryByKeysValues, TopologySetDictionary +from . import VertexNearestVertex, DictionaryValueAtKey, DictionaryByKeysValues, TopologySetDictionary, TopologyAddApertures import json # From https://stackabuse.com/python-how-to-flatten-list-of-lists/ @@ -384,28 +384,28 @@ def processItem(item): _ = topology.Cells(None, cells) except: pass - processApertures(cells, topologic.Cluster.ByTopologies(cellApertures), False, 0.001) + TopologyAddApertures.processApertures(cells, topologic.Cluster.ByTopologies(cellApertures), False, 0.0001) faceApertures = getApertures(jsonItem['faceApertures']) faces = [] try: _ = topology.Faces(None, faces) except: pass - processApertures(faces, topologic.Cluster.ByTopologies(faceApertures), False, 0.001) + TopologyAddApertures.processApertures(faces, topologic.Cluster.ByTopologies(faceApertures), False, 0.0001) edgeApertures = getApertures(jsonItem['edgeApertures']) edges = [] try: _ = topology.Edges(None, edges) except: pass - processApertures(edges, topologic.Cluster.ByTopologies(edgeApertures), False, 0.001) + TopologyAddApertures.processApertures(edges, topologic.Cluster.ByTopologies(edgeApertures), False, 0.0001) vertexApertures = getApertures(jsonItem['vertexApertures']) vertices = [] try: _ = topology.Vertices(None, vertices) except: pass - processApertures(vertices, topologic.Cluster.ByTopologies(vertexApertures), False, 0.001) + TopologyAddApertures.processApertures(vertices, topologic.Cluster.ByTopologies(vertexApertures), False, 0.001) cellDataList = jsonItem['cellDictionaries'] cellSelectors = [] for cellDataItem in cellDataList: diff --git a/nodes/Topologic/Version.py b/nodes/Topologic/Version.py index d2ecb21..d163571 100644 --- a/nodes/Topologic/Version.py +++ b/nodes/Topologic/Version.py @@ -18,7 +18,7 @@ def sv_init(self, context): self.outputs.new('SvStringsSocket', 'Version') def process(self): - self.outputs['Version'].sv_set(['0 8 2 5']) + self.outputs['Version'].sv_set(['0 8 2 6']) def register(): diff --git a/nodes/Topologic/VertexNearestFace.py b/nodes/Topologic/VertexNearestFace.py new file mode 100644 index 0000000..76f7cbf --- /dev/null +++ b/nodes/Topologic/VertexNearestFace.py @@ -0,0 +1,219 @@ +import bpy +from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty, BoolProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + +import topologic +import time +from . import Replication + +def matchLengths(list): + maxLength = len(list[0]) + for aSubList in list: + newLength = len(aSubList) + if newLength > maxLength: + maxLength = newLength + for anItem in list: + if (len(anItem) > 0): + itemToAppend = anItem[-1] + else: + itemToAppend = None + for i in range(len(anItem), maxLength): + anItem.append(itemToAppend) + return list + +# Adapted From https://johnlekberg.com/blog/2020-04-17-kd-tree.html +import collections +import operator +def SED(a, b): + """Compute the squared Euclidean distance between X and Y.""" + p1 = (a.X(), a.Y(), a.Z()) + p2 = (b.X(), b.Y(), b.Z()) + return sum((i-j)**2 for i, j in zip(p1, p2)) + +BT = collections.namedtuple("BT", ["value", "left", "right"]) +BT.__doc__ = """ +A Binary Tree (BT) with a node value, and left- and +right-subtrees. +""" +def firstItem(v): + return v.X() +def secondItem(v): + return v.Y() +def thirdItem(v): + return v.Z() + +def itemAtIndex(v, index): + if index == 0: + return v.X() + elif index == 1: + return v.Y() + elif index == 2: + return v.Z() + +def sortList(vertices, index): + if index == 0: + vertices.sort(key=firstItem) + elif index == 1: + vertices.sort(key=secondItem) + elif index == 2: + vertices.sort(key=thirdItem) + return vertices + +def kdtree(topology): + assert isinstance(topology, topologic.Topology), "Vertex.NearestVertex: The input is not a Topology." + vertices = [] + _ = topology.Vertices(None, vertices) + assert (len(vertices) > 0), "Vertex.NearestVertex: Could not find any vertices in the input Topology" + + """Construct a k-d tree from an iterable of vertices. + + This algorithm is taken from Wikipedia. For more details, + + > https://en.wikipedia.org/wiki/K-d_tree#Construction + + """ + # k = len(points[0]) + k = 3 + + def build(*, vertices, depth): + if len(vertices) == 0: + return None + #points.sort(key=operator.itemgetter(depth % k)) + vertices = sortList(vertices, (depth % k)) + + middle = len(vertices) // 2 + + return BT( + value = vertices[middle], + left = build( + vertices=vertices[:middle], + depth=depth+1, + ), + right = build( + vertices=vertices[middle+1:], + depth=depth+1, + ), + ) + + return build(vertices=list(vertices), depth=0) + +NNRecord = collections.namedtuple("NNRecord", ["vertex", "distance"]) +NNRecord.__doc__ = """ +Used to keep track of the current best guess during a nearest +neighbor search. +""" + +def find_nearest_neighbor(*, tree, vertex): + """Find the nearest neighbor in a k-d tree for a given vertex. + """ + k = 3 # Forcing k to be 3 dimensional + best = None + def search(*, tree, depth): + """Recursively search through the k-d tree to find the nearest neighbor. + """ + nonlocal best + + if tree is None: + return + distance = SED(tree.value, vertex) + if best is None or distance < best.distance: + best = NNRecord(vertex=tree.value, distance=distance) + + axis = depth % k + diff = itemAtIndex(vertex,axis) - itemAtIndex(tree.value,axis) + if diff <= 0: + close, away = tree.left, tree.right + else: + close, away = tree.right, tree.left + + search(tree=close, depth=depth+1) + if diff**2 < best.distance: + search(tree=away, depth=depth+1) + + search(tree=tree, depth=0) + return best.vertex + +def processItem(input): + vertex, topology, useKDTree = input + if useKDTree: + tree = kdtree(topology) + return find_nearest_neighbor(tree=tree, vertex=vertex) + else: + vertices = [] + _ = topology.Vertices(None, vertices) + distances = [] + indices = [] + for i in range(len(vertices)): + distances.append(SED(vertex, vertices[i])) + indices.append(i) + sorted_indices = [x for _, x in sorted(zip(distances, indices))] + return vertices[sorted_indices[0]] + +replication = [("Default", "Default", "", 1),("Trim", "Trim", "", 2),("Iterate", "Iterate", "", 3),("Repeat", "Repeat", "", 4),("Interlace", "Interlace", "", 5)] + +class SvVertexNearestVertex(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Topologic + Tooltip: Outputs the nearest Vertex to the input Vertex from the list of input Vertices + """ + bl_idname = 'SvVertexNearestVertex' + bl_label = 'Vertex.NearestVertex' + bl_icon = 'SELECT_DIFFERENCE' + UseKDTree: BoolProperty(name="UseKDTree", default=False, update=updateNode) + Replication: EnumProperty(name="Replication", description="Replication", default="Default", items=replication, update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'Vertex') + self.inputs.new('SvStringsSocket', 'Topology') + self.inputs.new('SvStringsSocket', 'Use k-d Tree').prop_name = 'UseKDTree' + self.outputs.new('SvStringsSocket', 'Vertex') + self.width = 175 + for socket in self.inputs: + if socket.prop_name != '': + socket.custom_draw = "draw_sockets" + + def draw_sockets(self, socket, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text=(socket.name or "Untitled") + f". {socket.objects_number or ''}") + split.row().prop(self, socket.prop_name, text="") + + def draw_buttons(self, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Replication") + split.row().prop(self, "Replication",text="") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + inputs_nested = [] + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_nested.append(inp) + inputs_flat.append(Replication.flatten(inp)) + inputs_replicated = Replication.replicateInputs(inputs_flat, self.Replication) + outputs = [] + for anInput in inputs_replicated: + outputs.append(processItem(anInput)) + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_flat.append(Replication.flatten(inp)) + if self.Replication == "Interlace": + outputs = Replication.re_interlace(outputs, inputs_flat) + else: + match_list = Replication.best_match(inputs_nested, inputs_flat, self.Replication) + outputs = Replication.unflatten(outputs, match_list) + if len(outputs) == 1: + if isinstance(outputs[0], list): + outputs = outputs[0] + self.outputs['Vertex'].sv_set(outputs) + +def register(): + bpy.utils.register_class(SvVertexNearestVertex) + +def unregister(): + bpy.utils.unregister_class(SvVertexNearestVertex) diff --git a/nodes/Topologic/VertexNearestTopology.py b/nodes/Topologic/VertexNearestTopology.py new file mode 100644 index 0000000..fa6bfd8 --- /dev/null +++ b/nodes/Topologic/VertexNearestTopology.py @@ -0,0 +1,238 @@ +import bpy +from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty, BoolProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + +import topologic +import time +from . import Replication +from . import TopologySubTopologies + +def matchLengths(list): + maxLength = len(list[0]) + for aSubList in list: + newLength = len(aSubList) + if newLength > maxLength: + maxLength = newLength + for anItem in list: + if (len(anItem) > 0): + itemToAppend = anItem[-1] + else: + itemToAppend = None + for i in range(len(anItem), maxLength): + anItem.append(itemToAppend) + return list + +# Adapted From https://johnlekberg.com/blog/2020-04-17-kd-tree.html +import collections +import operator +def SED(a, b): + return topologic.VertexUtility.Distance(a, b) + +BT = collections.namedtuple("BT", ["value", "left", "right"]) +BT.__doc__ = """ +A Binary Tree (BT) with a node value, and left- and +right-subtrees. +""" +def firstItem(topology): + return topology.Centroid().X() +def secondItem(topology): + return topology.Centroid().Y() +def thirdItem(topology): + return topology.Centroid().Z() + +def itemAtIndex(topology, index): + v = topology.Centroid() + if index == 0: + return v.X() + elif index == 1: + return v.Y() + elif index == 2: + return v.Z() + +def sortList(vertices, index): + if index == 0: + vertices.sort(key=firstItem) + elif index == 1: + vertices.sort(key=secondItem) + elif index == 2: + vertices.sort(key=thirdItem) + return vertices + +def kdtree(topology, topologyType): + subTopologies = TopologySubTopologies.processItem([topology, topologyType]) + assert (len(subTopologies) > 0), "Vertex.NearestTopology: Could not find any of subTopologies of the specified type in the input Topology" + + """Construct a k-d tree from an iterable of subTopologies. + + This algorithm is taken from Wikipedia. For more details, + + > https://en.wikipedia.org/wiki/K-d_tree#Construction + + """ + # k = len(points[0]) + k = 3 + + def build(*, subTopologies, depth): + if len(subTopologies) == 0: + return None + #points.sort(key=operator.itemgetter(depth % k)) + subTopologies = sortList(subTopologies, (depth % k)) + + middle = len(subTopologies) // 2 + + return BT( + value = subTopologies[middle], + left = build( + subTopologies=subTopologies[:middle], + depth=depth+1, + ), + right = build( + subTopologies=subTopologies[middle+1:], + depth=depth+1, + ), + ) + + return build(subTopologies=list(subTopologies), depth=0) + +NNRecord = collections.namedtuple("NNRecord", ["vertex", "distance"]) +NNRecord.__doc__ = """ +Used to keep track of the current best guess during a nearest +neighbor search. +""" + +def find_nearest_neighbor(*, tree, vertex): + """Find the nearest neighbor in a k-d tree for a given vertex. + """ + k = 3 # Forcing k to be 3 dimensional + best = None + def search(*, tree, depth): + """Recursively search through the k-d tree to find the nearest neighbor. + """ + nonlocal best + + if tree is None: + return + distance = SED(vertex, tree.value) + if best is None or distance < best.distance: + best = NNRecord(vertex=tree.value, distance=distance) + + axis = depth % k + diff = itemAtIndex(vertex,axis) - itemAtIndex(tree.value,axis) + if diff <= 0: + close, away = tree.left, tree.right + else: + close, away = tree.right, tree.left + + search(tree=close, depth=depth+1) + if diff**2 < best.distance: + search(tree=away, depth=depth+1) + + search(tree=tree, depth=0) + return best.vertex + +saved_cluster = None +saved_tree = None +def processItem(input): + vertex, cluster, useKDTree, topologyType = input + global saved_cluster + global saved_tree + tree = None + if useKDTree: + if not saved_cluster: + saved_cluster = cluster + tree = kdtree(cluster, topologyType) + saved_tree = tree + else: + if saved_cluster == cluster: + tree = saved_tree + else: + tree = kdtree(cluster, topologyType) + saved_tree = tree + return find_nearest_neighbor(tree=tree, vertex=vertex) + else: + distances = [] + indices = [] + subTopologies = TopologySubTopologies.processItem([cluster, topologyType]) + for i in range(len(subTopologies)): + distances.append(SED(vertex, subTopologies[i])) + indices.append(i) + sorted_indices = [x for _, x in sorted(zip(distances, indices))] + return subTopologies[sorted_indices[0]] + +replication = [("Default", "Default", "", 1),("Trim", "Trim", "", 2),("Iterate", "Iterate", "", 3),("Repeat", "Repeat", "", 4),("Interlace", "Interlace", "", 5)] +topologyTypes = [("Vertex", "Vertex", "", 1),("Edge", "Edge", "", 2),("Wire", "Wire", "", 3),("Face", "Face", "", 4),("Shell", "Shell", "", 5), ("Cell", "Cell", "", 6),("CellComplex", "CellComplex", "", 7), ("Cluster", "Cluster", "", 8), ("Aperture", "Aperture", "", 9)] + +class SvVertexNearestTopology(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Topologic + Tooltip: Outputs the nearest Topology to the input Vertex from the input Cluster + """ + bl_idname = 'SvVertexNearestTopology' + bl_label = 'Vertex.NearestTopology' + bl_icon = 'SELECT_DIFFERENCE' + UseKDTree: BoolProperty(name="UseKDTree", default=False, update=updateNode) + Replication: EnumProperty(name="Replication", description="Replication", default="Default", items=replication, update=updateNode) + TopologyType: EnumProperty(name="Topology Type", description="Specify topology type", default="Vertex", items=topologyTypes, update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'Vertex') + self.inputs.new('SvStringsSocket', 'Topology Cluster') + self.inputs.new('SvStringsSocket', 'Use k-d Tree').prop_name = 'UseKDTree' + self.outputs.new('SvStringsSocket', 'Topology') + self.width = 175 + for socket in self.inputs: + if socket.prop_name != '': + socket.custom_draw = "draw_sockets" + + def draw_sockets(self, socket, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text=(socket.name or "Untitled") + f". {socket.objects_number or ''}") + split.row().prop(self, socket.prop_name, text="") + + def draw_buttons(self, context, layout): + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="Replication") + split.row().prop(self, "Replication",text="") + row = layout.row() + split = row.split(factor=0.5) + split.row().label(text="TopologyType") + split.row().prop(self, "TopologyType",text="") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + inputs_nested = [] + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_nested.append(inp) + inputs_flat.append(Replication.flatten(inp)) + inputs_nested.append([self.TopologyType]) + inputs_flat.append([self.TopologyType]) + inputs_replicated = Replication.replicateInputs(inputs_flat, self.Replication) + outputs = [] + for anInput in inputs_replicated: + outputs.append(processItem(anInput)) + inputs_flat = [] + for anInput in self.inputs: + inp = anInput.sv_get(deepcopy=True) + inputs_flat.append(Replication.flatten(inp)) + inputs_flat.append([self.TopologyType]) + if self.Replication == "Interlace": + outputs = Replication.re_interlace(outputs, inputs_flat) + else: + match_list = Replication.best_match(inputs_nested, inputs_flat, self.Replication) + outputs = Replication.unflatten(outputs, match_list) + if len(outputs) == 1: + if isinstance(outputs[0], list): + outputs = outputs[0] + self.outputs['Topology'].sv_set(outputs) + +def register(): + bpy.utils.register_class(SvVertexNearestTopology) + +def unregister(): + bpy.utils.unregister_class(SvVertexNearestTopology) diff --git a/nodes/Topologic/WireCircle.py b/nodes/Topologic/WireCircle.py index 8e62d0c..914170e 100644 --- a/nodes/Topologic/WireCircle.py +++ b/nodes/Topologic/WireCircle.py @@ -36,7 +36,6 @@ def processItem(item): dirY, \ dirZ, \ placement = item - print("Circle Origin", origin.X(), origin.Y(), origin.Z()) baseV = [] xList = [] yList = [] @@ -45,12 +44,13 @@ def processItem(item): toAngle += 360 elif toAngle == fromAngle: raise Exception("Wire.Circle - Error: To angle cannot be equal to the From Angle") - angleRange = toAngle - fromAngle fromAngle = math.radians(fromAngle) toAngle = math.radians(toAngle) + angleRange = toAngle - fromAngle + sides = int(math.floor(sides)) for i in range(sides+1): - angle = fromAngle + math.radians(angleRange/sides)*i + angle = fromAngle + angleRange/sides*i x = math.sin(angle)*radius + origin.X() y = math.cos(angle)*radius + origin.Y() z = origin.Z() diff --git a/nodes/Topologic/WireProject.py b/nodes/Topologic/WireProject.py index 66eaf19..c54415d 100644 --- a/nodes/Topologic/WireProject.py +++ b/nodes/Topologic/WireProject.py @@ -20,6 +20,8 @@ def projectVertex(vertex, face, vList): def processItem(item): wire, face, direction = item + print("DIRECTION:",direction) + print("DIRECTION.X()", direction.X()) large_face = topologic.TopologyUtility.Scale(face, face.CenterOfMass(), 500, 500, 500) try: vList = [direction.X(), direction.Y(), direction.Z()] @@ -41,8 +43,8 @@ def processItem(item): sv = edge.StartVertex() ev = edge.EndVertex() - psv = projectVertex(sv, large_face, direction) - pev = projectVertex(ev, large_face, direction) + psv = projectVertex(sv, large_face, vList) + pev = projectVertex(ev, large_face, vList) if psv and pev: try: pe = topologic.Edge.ByStartVertexEndVertex(psv, pev) diff --git a/nodes/Topologic/__pycache__/FaceTrimByWire.cpython-310.pyc b/nodes/Topologic/__pycache__/FaceTrimByWire.cpython-310.pyc index 76b3649..0d2b5b4 100644 Binary files a/nodes/Topologic/__pycache__/FaceTrimByWire.cpython-310.pyc and b/nodes/Topologic/__pycache__/FaceTrimByWire.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/InstallDependencies.cpython-310.pyc b/nodes/Topologic/__pycache__/InstallDependencies.cpython-310.pyc index f6e7cf5..79d083c 100644 Binary files a/nodes/Topologic/__pycache__/InstallDependencies.cpython-310.pyc and b/nodes/Topologic/__pycache__/InstallDependencies.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/ShellPie.cpython-310.pyc b/nodes/Topologic/__pycache__/ShellPie.cpython-310.pyc new file mode 100644 index 0000000..d9ae21c Binary files /dev/null and b/nodes/Topologic/__pycache__/ShellPie.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/TopologyAddApertures.cpython-310.pyc b/nodes/Topologic/__pycache__/TopologyAddApertures.cpython-310.pyc index e70e020..cc83aa6 100644 Binary files a/nodes/Topologic/__pycache__/TopologyAddApertures.cpython-310.pyc and b/nodes/Topologic/__pycache__/TopologyAddApertures.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/TopologyBoundingBox.cpython-310.pyc b/nodes/Topologic/__pycache__/TopologyBoundingBox.cpython-310.pyc index 613d3b3..6f97533 100644 Binary files a/nodes/Topologic/__pycache__/TopologyBoundingBox.cpython-310.pyc and b/nodes/Topologic/__pycache__/TopologyBoundingBox.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/TopologyByImportedJSONMK1.cpython-310.pyc b/nodes/Topologic/__pycache__/TopologyByImportedJSONMK1.cpython-310.pyc index 631cd51..ff70645 100644 Binary files a/nodes/Topologic/__pycache__/TopologyByImportedJSONMK1.cpython-310.pyc and b/nodes/Topologic/__pycache__/TopologyByImportedJSONMK1.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/TopologyTranslate.cpython-310.pyc b/nodes/Topologic/__pycache__/TopologyTranslate.cpython-310.pyc index 7557570..15a7078 100644 Binary files a/nodes/Topologic/__pycache__/TopologyTranslate.cpython-310.pyc and b/nodes/Topologic/__pycache__/TopologyTranslate.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/Version.cpython-310.pyc b/nodes/Topologic/__pycache__/Version.cpython-310.pyc index 38eb64b..10a9725 100644 Binary files a/nodes/Topologic/__pycache__/Version.cpython-310.pyc and b/nodes/Topologic/__pycache__/Version.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/VertexNearestTopology.cpython-310.pyc b/nodes/Topologic/__pycache__/VertexNearestTopology.cpython-310.pyc new file mode 100644 index 0000000..565bc67 Binary files /dev/null and b/nodes/Topologic/__pycache__/VertexNearestTopology.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/WireCircle.cpython-310.pyc b/nodes/Topologic/__pycache__/WireCircle.cpython-310.pyc index ab8d5c7..f58fdf4 100644 Binary files a/nodes/Topologic/__pycache__/WireCircle.cpython-310.pyc and b/nodes/Topologic/__pycache__/WireCircle.cpython-310.pyc differ diff --git a/nodes/Topologic/__pycache__/WireProject.cpython-310.pyc b/nodes/Topologic/__pycache__/WireProject.cpython-310.pyc index 2dfa523..a4e9aed 100644 Binary files a/nodes/Topologic/__pycache__/WireProject.cpython-310.pyc and b/nodes/Topologic/__pycache__/WireProject.cpython-310.pyc differ