topologicpy 0.7.21__py3-none-any.whl → 0.7.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
topologicpy/Topology.py CHANGED
@@ -330,8 +330,8 @@ class Topology():
330
330
  The input topology with the input dictionary added to it.
331
331
 
332
332
  """
333
-
334
333
  from topologicpy.Dictionary import Dictionary
334
+
335
335
  if not Topology.IsInstance(topology, "Topology"):
336
336
  print("Topology.AddDictionary - Error: the input topology parameter is not a valid topology. Returning None.")
337
337
  return None
@@ -820,7 +820,6 @@ class Topology():
820
820
  apertures += Topology.Apertures(subTopology, subTopologyType=None)
821
821
  return apertures
822
822
 
823
-
824
823
  @staticmethod
825
824
  def ApertureTopologies(topology, subTopologyType=None):
826
825
  """
@@ -2139,6 +2138,7 @@ class Topology():
2139
2138
  The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
2140
2139
  excludeTypes : list , optional
2141
2140
  The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
2141
+
2142
2142
  Returns
2143
2143
  -------
2144
2144
  list
@@ -2706,141 +2706,305 @@ class Topology():
2706
2706
  return data
2707
2707
 
2708
2708
  @staticmethod
2709
- def ByOBJString(string, transposeAxes = True, progressBar=False, tolerance=0.0001):
2709
+ def ByOBJFile(objFile, mtlFile = None,
2710
+ defaultColor: list = [255,255,255],
2711
+ defaultOpacity: float = 1.0,
2712
+ transposeAxes: bool = True,
2713
+ removeCoplanarFaces: bool = True,
2714
+
2715
+ mantissa : int = 6,
2716
+ tolerance: float = 0.0001):
2710
2717
  """
2711
- Creates a topology from the input Wavefront OBJ string. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.
2718
+ Imports a topology from an OBJ file and an associated materials file.
2719
+ This method is basic and does not support textures and vertex normals.
2712
2720
 
2713
2721
  Parameters
2714
2722
  ----------
2715
- string : str
2716
- The input OBJ string.
2723
+ objFile : file object
2724
+ The OBJ file.
2725
+ mtlFile : file object , optional
2726
+ The MTL file. The default is None.
2727
+ defaultColor : list , optional
2728
+ The default color to use if none is specified in the file. The default is [255, 255, 255] (white).
2729
+ defaultOpacity : float , optional
2730
+ The default opacity to use if none is specified in the file. The default is 1.0 (fully opaque).
2717
2731
  transposeAxes : bool , optional
2718
- If set to True the Z and Y coordinates are transposed so that Y points "up"
2719
- progressBar : bool , optional
2720
- If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
2732
+ If set to True the Z and Y axes are transposed. Otherwise, they are not. The default is True.
2733
+ removeCoplanarFaces : bool , optional
2734
+ If set to True, coplanar faces are merged. The default is True.
2735
+ mantissa : int , optional
2736
+ The desired length of the mantissa. The default is 6.
2721
2737
  tolerance : float , optional
2722
- The desired tolerance. The default is 0.0001.
2738
+ The desired tolerance. The default is 0.0001
2723
2739
 
2724
2740
  Returns
2725
2741
  -------
2726
- topology
2727
- The created topology.
2742
+ list
2743
+ The imported topologies.
2728
2744
 
2729
2745
  """
2730
- from topologicpy.Vertex import Vertex
2731
- from tqdm.auto import tqdm
2746
+ from os.path import dirname, join, exists
2732
2747
 
2733
- def parse(lines):
2734
- vertices = []
2735
- faces = []
2736
- for i in range(len(lines)):
2737
- l = lines[i].replace(",", " ")
2738
- s = l.split()
2739
- if isinstance(s, list):
2740
- if len(s) > 3:
2741
- if s[0].lower() == "v":
2742
- vertices.append([float(s[1]), float(s[2]), float(s[3])])
2743
- elif s[0].lower() == "f":
2744
- temp_faces = []
2745
- for j in range(1,len(s)):
2746
- f = s[j].split("/")[0]
2747
- temp_faces.append(int(f)-1)
2748
- faces.append(temp_faces)
2749
- return [vertices, faces]
2750
-
2751
- def parsetqdm(lines):
2752
- vertices = []
2753
- faces = []
2754
- for i in tqdm(range(len(lines))):
2755
- s = lines[i].split()
2756
- if isinstance(s, list):
2757
- if len(s) > 3:
2758
- if s[0].lower() == "v":
2759
- vertices.append([float(s[1]), float(s[2]), float(s[3])])
2760
- elif s[0].lower() == "f":
2761
- temp_faces = []
2762
- for j in range(1,len(s)):
2763
- f = s[j].split("/")[0]
2764
- temp_faces.append(int(f)-1)
2765
- faces.append(temp_faces)
2766
- return [vertices, faces]
2767
-
2768
-
2769
- lines = string.split("\n")
2770
- if lines:
2771
- if progressBar:
2772
- vertices, faces = parsetqdm(lines)
2773
- else:
2774
- vertices, faces = parse(lines)
2775
- if vertices or faces:
2776
- topology = Topology.ByGeometry(vertices = vertices, faces = faces, outputMode="default", tolerance=tolerance)
2777
- if transposeAxes == True:
2778
- topology = Topology.Rotate(topology, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
2779
- return Topology.SelfMerge(topology)
2780
- print("Topology.ByOBJString - Error: Could not find vertices or faces. Returning None.")
2781
- return None
2748
+
2749
+ def find_next_word_after_mtllib(text):
2750
+ words = text.split()
2751
+ for i, word in enumerate(words):
2752
+ if word == 'mtllib' and i + 1 < len(words):
2753
+ return words[i + 1]
2754
+ return None
2755
+
2756
+ obj_string = objFile.read()
2757
+ mtl_filename = find_next_word_after_mtllib(obj_string)
2758
+ mtl_string = None
2759
+ if mtlFile:
2760
+ mtl_string = mtlFile.read()
2761
+ return Topology.ByOBJString(obj_string, mtl_string,
2762
+ defaultColor=defaultColor, defaultOpacity=defaultOpacity,
2763
+ transposeAxes=transposeAxes, removeCoplanarFaces=removeCoplanarFaces,
2764
+ mantissa=mantissa, tolerance=tolerance)
2782
2765
 
2783
2766
  @staticmethod
2784
- def ByOBJFile(file, transposeAxes=True, progressBar=False, tolerance=0.0001):
2767
+ def ByOBJPath(objPath,
2768
+ defaultColor: list = [255,255,255], defaultOpacity: float = 1.0,
2769
+ transposeAxes: bool = True, removeCoplanarFaces: bool = True,
2770
+ mantissa : int = 6, tolerance: float = 0.0001):
2785
2771
  """
2786
- Imports the topology from a Wevefront OBJ file. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.
2772
+ Imports a topology from an OBJ file path and an associated materials file.
2773
+ This method is basic and does not support textures and vertex normals.
2787
2774
 
2788
2775
  Parameters
2789
2776
  ----------
2790
- file : file object
2791
- The input OBJ file.
2777
+ objPath : str
2778
+ The path to the OBJ file.
2779
+ defaultColor : list , optional
2780
+ The default color to use if none is pecified in the file. The default is [255, 255, 255] (white).
2781
+ defaultOpacity : float , optional
2782
+ The default opacity to use if none is pecified in the file. The default is 1.0 (fully opaque).
2792
2783
  transposeAxes : bool , optional
2793
- If set to True the Z and Y coordinates are transposed so that Y points "up"
2794
- progressBar : bool , optional
2795
- If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
2784
+ If set to True the Z and Y axes are transposed. Otherwise, they are not. The default is True.
2785
+ removeCoplanarFaces : bool , optional
2786
+ If set to True, coplanar faces are merged. The default is True.
2787
+ mantissa : int , optional
2788
+ The desired length of the mantissa. The default is 6.
2796
2789
  tolerance : float , optional
2797
- The desired tolerance. The default is 0.0001.
2790
+ The desired tolerance. The default is 0.0001
2798
2791
 
2799
2792
  Returns
2800
2793
  -------
2801
- topology
2802
- The imported topology.
2794
+ list
2795
+ The imported topologies.
2803
2796
 
2804
2797
  """
2805
- if not file:
2806
- print("Topology.ByOBJFile - Error: the input file parameter is not a valid file. Returning None.")
2798
+ from os.path import dirname, join, exists
2799
+
2800
+ if not objPath:
2801
+ print("Topology.ByOBJPath - Error: the input OBJ path parameter is not a valid path. Returning None.")
2807
2802
  return None
2808
- obj_string = file.read()
2809
- topology = Topology.ByOBJString(obj_string, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
2810
- file.close()
2811
- return topology
2812
-
2803
+ if not exists(objPath):
2804
+ print("Topology.ByOBJPath - Error: the input OBJ path does not exist. Returning None.")
2805
+ return None
2806
+
2807
+ def find_next_word_after_mtllib(text):
2808
+ words = text.split()
2809
+ for i, word in enumerate(words):
2810
+ if word == 'mtllib' and i + 1 < len(words):
2811
+ return words[i + 1]
2812
+ return None
2813
+
2814
+ with open(objPath, 'r') as obj_file:
2815
+ obj_string = obj_file.read()
2816
+ mtl_filename = find_next_word_after_mtllib(obj_string)
2817
+ mtl_string = None
2818
+ if mtl_filename:
2819
+ parent_folder = dirname(objPath)
2820
+ mtl_path = join(parent_folder, mtl_filename)
2821
+ if exists(mtl_path):
2822
+ with open(mtl_path, 'r') as mtl_file:
2823
+ mtl_string = mtl_file.read()
2824
+ return Topology.ByOBJString(obj_string, mtl_string,
2825
+ defaultColor=defaultColor, defaultOpacity=defaultOpacity,
2826
+ transposeAxes=transposeAxes, removeCoplanarFaces=removeCoplanarFaces,
2827
+ mantissa=mantissa, tolerance=tolerance)
2828
+
2813
2829
  @staticmethod
2814
- def ByOBJPath(path, transposeAxes=True, progressBar=False, tolerance=0.0001):
2830
+ def ByOBJString(objString: str, mtlString: str = None,
2831
+ defaultColor: list = [255,255,255], defaultOpacity: float = 1.0,
2832
+ transposeAxes: bool = True, removeCoplanarFaces: bool = False,
2833
+ mantissa = 6, tolerance = 0.0001):
2815
2834
  """
2816
- Imports the topology from a Wevefront OBJ file path. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.
2835
+ Imports a topology from OBJ and MTL strings.
2817
2836
 
2818
2837
  Parameters
2819
2838
  ----------
2820
- path : str
2821
- The file path to the OBJ file.
2839
+ objString : str
2840
+ The string of the OBJ file.
2841
+ mtlString : str , optional
2842
+ The string of the MTL file. The default is None.
2843
+ defaultColor : list , optional
2844
+ The default color to use if none is pecified in the string. The default is [255, 255, 255] (white).
2845
+ defaultOpacity : float , optional
2846
+ The default opacity to use if none is pecified in the string. The default is 1.0 (fully opaque).
2822
2847
  transposeAxes : bool , optional
2823
- If set to True the Z and Y coordinates are transposed so that Y points "up".
2824
- progressBar : bool , optional
2825
- If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
2848
+ If set to True the Z and Y axes are transposed. Otherwise, they are not. The default is True.
2849
+ removeCoplanarFaces : bool , optional
2850
+ If set to True, coplanar faces are merged. The default is True.
2851
+ mantissa : int , optional
2852
+ The desired length of the mantissa. The default is 6.
2826
2853
  tolerance : float , optional
2827
- The desired tolerance. The default is 0.0001.
2854
+ The desired tolerance. The default is 0.0001
2828
2855
 
2829
2856
  Returns
2830
2857
  -------
2831
- topology
2832
- The imported topology.
2858
+ list
2859
+ The imported topologies.
2833
2860
 
2834
2861
  """
2835
- if not path:
2836
- print("Topology.ByOBJPath - Error: the input path parameter is not a valid path. Returning None.")
2837
- return None
2838
- try:
2839
- file = open(path)
2840
- except:
2841
- print("Topology.ByOBJPath - Error: the OBJ file is not a valid file. Returning None.")
2842
- return None
2843
- return Topology.ByOBJFile(file, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
2862
+ from topologicpy.Vertex import Vertex
2863
+ from topologicpy.Edge import Edge
2864
+ from topologicpy.Wire import Wire
2865
+ from topologicpy.Face import Face
2866
+ from topologicpy.Shell import Shell
2867
+ from topologicpy.Cell import Cell
2868
+ from topologicpy.Cluster import Cluster
2869
+ from topologicpy.Topology import Topology
2870
+ from topologicpy.Dictionary import Dictionary
2871
+ from topologicpy.Helper import Helper
2872
+
2873
+ def load_materials(mtl_string):
2874
+ materials = {}
2875
+ if not mtl_string:
2876
+ return materials
2877
+ current_material = None
2878
+ lines = mtlString.split('\n')
2879
+ for line in lines:
2880
+ line = line.strip()
2881
+ if line.startswith('#') or not line:
2882
+ continue
2883
+ parts = line.split()
2884
+ if not parts:
2885
+ continue
2886
+ if parts[0] == 'newmtl':
2887
+ current_material = parts[1]
2888
+ materials[current_material] = {}
2889
+ elif current_material:
2890
+ if parts[0] == 'Kd': # Diffuse color
2891
+ materials[current_material]['Kd'] = list(map(float, parts[1:4]))
2892
+ elif parts[0] == 'Ka': # Ambient color
2893
+ materials[current_material]['Ka'] = list(map(float, parts[1:4]))
2894
+ elif parts[0] == 'Ks': # Specular color
2895
+ materials[current_material]['Ks'] = list(map(float, parts[1:4]))
2896
+ elif parts[0] == 'Ns': # Specular exponent
2897
+ materials[current_material]['Ns'] = float(parts[1])
2898
+ elif parts[0] == 'd': # Transparency
2899
+ materials[current_material]['d'] = float(parts[1])
2900
+ elif parts[0] == 'map_Kd': # Diffuse texture map
2901
+ materials[current_material]['map_Kd'] = parts[1]
2902
+ # Add more properties as needed
2903
+ return materials
2904
+
2905
+ materials = load_materials(mtlString)
2906
+ vertices = []
2907
+ textures = []
2908
+ normals = []
2909
+ groups = {}
2910
+ current_group = None
2911
+ current_material = None
2912
+ lines = objString.split('\n')
2913
+ for line in lines:
2914
+ line = line.strip()
2915
+ if line.startswith('#'):
2916
+ continue
2917
+
2918
+ parts = line.split()
2919
+ if not parts:
2920
+ continue
2921
+
2922
+ if parts[0] == 'v':
2923
+ vertex = list(map(float, parts[1:4]))
2924
+ vertex = [round(coord, mantissa) for coord in vertex]
2925
+ if transposeAxes == True:
2926
+ vertex = [vertex[0], vertex[2], vertex[1]]
2927
+ vertices.append(vertex)
2928
+ elif parts[0] == 'vt':
2929
+ texture = list(map(float, parts[1:3]))
2930
+ textures.append(texture)
2931
+ elif parts[0] == 'vn':
2932
+ normal = list(map(float, parts[1:4]))
2933
+ normals.append(normal)
2934
+ elif parts[0] == 'f':
2935
+ face = []
2936
+ for part in parts[1:]:
2937
+ indices = part.split('/')
2938
+ vertex_index = int(indices[0]) - 1 if indices[0] else None
2939
+ texture_index = int(indices[1]) - 1 if len(indices) > 1 and indices[1] else None
2940
+ normal_index = int(indices[2]) - 1 if len(indices) > 2 and indices[2] else None
2941
+ face.append((vertex_index, texture_index, normal_index))
2942
+
2943
+ if current_group not in groups:
2944
+ groups[current_group] = []
2945
+ groups[current_group].append((face, current_material))
2946
+ elif parts[0] == 'usemtl':
2947
+ current_material = parts[1]
2948
+ elif parts[0] == 'g' or parts[0] == 'o':
2949
+ current_group = parts[1] if len(parts) > 1 else None
2950
+
2951
+ obj_data = {
2952
+ 'vertices': vertices,
2953
+ 'textures': textures,
2954
+ 'normals': normals,
2955
+ 'materials': materials,
2956
+ 'groups': groups
2957
+ }
2958
+ keys = obj_data.keys()
2959
+ groups = obj_data['groups']
2960
+ group_keys = groups.keys()
2961
+ vertices = obj_data['vertices']
2962
+ groups = obj_data['groups']
2963
+ materials = obj_data['materials']
2964
+ names = list(groups.keys())
2965
+ return_topologies = []
2966
+ for i in range(len(names)):
2967
+ object_faces = []
2968
+ face_selectors = []
2969
+ object_name = names[i]
2970
+ faces = groups[object_name]
2971
+ f = faces[0] # Get object material from first face. Assume it is the material of the group
2972
+ object_color = defaultColor
2973
+ object_opacity = defaultOpacity
2974
+ object_material = None
2975
+ if len(f) >= 2:
2976
+ object_material = f[1]
2977
+ if object_material in materials.keys():
2978
+ object_color = materials[object_material]['Kd']
2979
+ object_color = [int(round(c*255,0)) for c in object_color]
2980
+ object_opacity = materials[object_material]['d']
2981
+ for f in faces:
2982
+ indices = f[0]
2983
+ face_material = f[1]
2984
+ face_indices = []
2985
+ for coordinate in indices:
2986
+ face_indices.append(coordinate[0])
2987
+ face = Topology.ByGeometry(vertices=vertices, faces=[face_indices])
2988
+ object_faces.append(face)
2989
+ if not face_material == object_material:
2990
+ if face_material in materials.keys():
2991
+ face_color = materials[face_material]['Kd']
2992
+ face_color = [int(round(c*255,0)) for c in face_color]
2993
+ face_opacity = materials[face_material]['d']
2994
+ selector = Face.InternalVertex(face)
2995
+ d = Dictionary.ByKeysValues(['color', 'opacity'], [face_color, face_opacity])
2996
+ selector = Topology.SetDictionary(selector, d)
2997
+ face_selectors.append(selector)
2998
+
2999
+ topology = Topology.SelfMerge(Cluster.ByTopologies(object_faces), tolerance=tolerance)
3000
+ if removeCoplanarFaces:
3001
+ topology = Topology.RemoveCoplanarFaces(topology, tolerance=tolerance)
3002
+ d = Dictionary.ByKeysValues(['name', 'color', 'opacity'], [object_name, object_color, object_opacity])
3003
+ topology = Topology.SetDictionary(topology, d)
3004
+ if len(face_selectors) > 0:
3005
+ topology = Topology.TransferDictionariesBySelectors(topology, selectors=face_selectors, tranFaces=True, tolerance=tolerance)
3006
+ return_topologies.append(topology)
3007
+ return return_topologies
2844
3008
 
2845
3009
  @staticmethod
2846
3010
  def ByOCCTShape(occtShape):
@@ -3854,7 +4018,6 @@ class Topology():
3854
4018
  return True
3855
4019
  return False
3856
4020
 
3857
-
3858
4021
  def ExportToDXF(topologies, path: str, overwrite: bool = False, mantissa: int = 6):
3859
4022
  """
3860
4023
  Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF.
@@ -4373,7 +4536,6 @@ class Topology():
4373
4536
  returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
4374
4537
  return returnDict
4375
4538
 
4376
-
4377
4539
  def getApertureData(topology, topLevel="False", uuidKey="uuid"):
4378
4540
  json_data = []
4379
4541
  if Topology.IsInstance(topology, "Vertex"):
@@ -4512,7 +4674,7 @@ class Topology():
4512
4674
  return json_string
4513
4675
 
4514
4676
  @staticmethod
4515
- def OBJString(topology, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001):
4677
+ def OBJString(topology, color, vertexIndex, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001):
4516
4678
  """
4517
4679
  Returns the Wavefront string of the input topology. This is very experimental and outputs a simple solid topology.
4518
4680
 
@@ -4520,6 +4682,10 @@ class Topology():
4520
4682
  ----------
4521
4683
  topology : topologic_core.Topology
4522
4684
  The input topology.
4685
+ color : list
4686
+ The desired color to assign to the topology
4687
+ vertexIndex : int
4688
+ The vertex index to use as the starting index.
4523
4689
  transposeAxes : bool , optional
4524
4690
  If set to True the Z and Y coordinates are transposed so that Y points "up"
4525
4691
  mode : int , optional
@@ -4548,17 +4714,16 @@ class Topology():
4548
4714
  The Wavefront OBJ string of the input topology
4549
4715
 
4550
4716
  """
4717
+
4551
4718
  from topologicpy.Helper import Helper
4552
- from topologicpy.Vertex import Vertex
4553
- from topologicpy.Face import Face
4554
4719
 
4555
4720
  if not Topology.IsInstance(topology, "Topology"):
4556
4721
  print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
4557
4722
  return None
4558
-
4723
+
4559
4724
  lines = []
4560
- version = Helper.Version()
4561
- lines.append("# topologicpy "+version)
4725
+ #version = Helper.Version()
4726
+ #lines.append("# topologicpy " + version)
4562
4727
  topology = Topology.Triangulate(topology, mode=mode, meshSize=meshSize, tolerance=tolerance)
4563
4728
  d = Topology.Geometry(topology, mantissa=mantissa)
4564
4729
  vertices = d['vertices']
@@ -4569,26 +4734,26 @@ class Topology():
4569
4734
  tVertices.append([v[0], v[2], v[1]])
4570
4735
  vertices = tVertices
4571
4736
  for v in vertices:
4572
- lines.append("v "+str(v[0])+" "+str(v[1])+" "+str(v[2]))
4737
+ lines.append("v " + str(v[0]) + " " + str(v[1]) + " " + str(v[2]))
4573
4738
  for f in faces:
4574
- line = "f"
4739
+ line = "usemtl " + str(color) + "\nf" # reference the material name
4575
4740
  for j in f:
4576
- line = line+" "+str(j+1)
4741
+ line = line + " " + str(j + vertexIndex)
4577
4742
  lines.append(line)
4578
4743
  finalLines = lines[0]
4579
- for i in range(1,len(lines)):
4580
- finalLines = finalLines+"\n"+lines[i]
4581
- return finalLines
4744
+ for i in range(1, len(lines)):
4745
+ finalLines = finalLines + "\n" + lines[i]
4746
+ return finalLines, len(vertices)
4582
4747
 
4583
4748
  @staticmethod
4584
- def ExportToOBJ(topology, path, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
4749
+ def ExportToOBJ(*topologies, path, nameKey="name", colorKey="color", opacityKey="opacity", defaultColor=[256,256,256], defaultOpacity=0.5, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
4585
4750
  """
4586
4751
  Exports the input topology to a Wavefront OBJ file. This is very experimental and outputs a simple solid topology.
4587
4752
 
4588
4753
  Parameters
4589
4754
  ----------
4590
- topology : topologic_core.Topology
4591
- The input topology.
4755
+ topologies : list or comma separated topologies
4756
+ The input list of topologies.
4592
4757
  path : str
4593
4758
  The input file path.
4594
4759
  transposeAxes : bool , optional
@@ -4621,11 +4786,21 @@ class Topology():
4621
4786
  True if the export operation is successful. False otherwise.
4622
4787
 
4623
4788
  """
4789
+ from topologicpy.Helper import Helper
4790
+ from topologicpy.Dictionary import Dictionary
4624
4791
  from os.path import exists
4625
4792
 
4626
- if not Topology.IsInstance(topology, "Topology"):
4627
- print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
4793
+ if isinstance(topologies, tuple):
4794
+ topologies = Helper.Flatten(list(topologies))
4795
+ if isinstance(topologies, list):
4796
+ new_topologies = [d for d in topologies if Topology.IsInstance(d, "Topology")]
4797
+ if len(new_topologies) == 0:
4798
+ print("Topology.ExportToOBJ - Error: the input topologies parameter does not contain any valid topologies. Returning None.")
4628
4799
  return None
4800
+ if not isinstance(new_topologies, list):
4801
+ print("Dictionary.ByMergedDictionaries - Error: The input dictionaries parameter is not a valid list. Returning None.")
4802
+ return None
4803
+
4629
4804
  if not overwrite and exists(path):
4630
4805
  print("Topology.ExportToOBJ - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
4631
4806
  return None
@@ -4635,10 +4810,40 @@ class Topology():
4635
4810
  if ext.lower() != ".obj":
4636
4811
  path = path+".obj"
4637
4812
  status = False
4638
- objString = Topology.OBJString(topology, transposeAxes=transposeAxes, mode=mode, meshSize=meshSize, mantissa=mantissa, tolerance=tolerance)
4639
- with open(path, "w") as f:
4640
- f.writelines(objString)
4641
- f.close()
4813
+
4814
+ mtl_path = path[:-4] + ".mtl"
4815
+
4816
+
4817
+ # Write out the material file
4818
+ n = max(len(str(len(topologies))), 3)
4819
+ with open(mtl_path, "w") as mtl_file:
4820
+ for i in range(len(new_topologies)):
4821
+ d = Topology.Dictionary(new_topologies[i])
4822
+ name = Dictionary.ValueAtKey(d, nameKey) or "Untitled_"+str(i).zfill(n)
4823
+ color = Dictionary.ValueAtKey(d, colorKey) or defaultColor
4824
+ color = [c/255 for c in color]
4825
+ opacity = Dictionary.ValueAtKey(d, opacityKey) or defaultOpacity
4826
+ mtl_file.write("newmtl color_" + str(i).zfill(n) + "\n")
4827
+ mtl_file.write("Kd " + ' '.join(map(str, color)) + "\n")
4828
+ mtl_file.write("d " + str(opacity) + "\n")
4829
+
4830
+ # Write out the obj file
4831
+ with open(path, "w") as obj_file:
4832
+ vertex_index = 1 # global vertex index counter
4833
+ obj_file.writelines("# topologicpy "+Helper.Version()+"\n")
4834
+ obj_file.writelines("mtllib " + mtl_path.split('/')[-1]) # reference the MTL file
4835
+ for i in range(len(topologies)):
4836
+ d = Topology.Dictionary(topologies[i])
4837
+ name = Dictionary.ValueAtKey(d, nameKey) or "Untitled_"+str(i).zfill(n)
4838
+ name = name.replace(" ", "_")
4839
+ obj_file.writelines("\ng "+name+"\n")
4840
+ result = Topology.OBJString(topologies[i], "color_" + str(i).zfill(n), vertex_index, transposeAxes=transposeAxes, mode=mode,
4841
+ meshSize=meshSize,
4842
+ mantissa=mantissa, tolerance=tolerance)
4843
+
4844
+ obj_file.writelines(result[0])
4845
+ vertex_index += result[1]
4846
+ obj_file.close()
4642
4847
  status = True
4643
4848
  return status
4644
4849
 
@@ -4779,25 +4984,6 @@ class Topology():
4779
4984
  """
4780
4985
  from topologicpy.Vertex import Vertex
4781
4986
  from topologicpy.Face import Face
4782
- from topologicpy.Vector import Vector
4783
-
4784
- def getSubTopologies(topology, subTopologyClass):
4785
- topologies = []
4786
- if subTopologyClass == topologic.Vertex:
4787
- _ = topology.Vertices(None, topologies)
4788
- elif subTopologyClass == topologic.Edge:
4789
- _ = topology.Edges(None, topologies)
4790
- elif subTopologyClass == topologic.Wire:
4791
- _ = topology.Wires(None, topologies)
4792
- elif subTopologyClass == topologic.Face:
4793
- _ = topology.Faces(None, topologies)
4794
- elif subTopologyClass == topologic.Shell:
4795
- _ = topology.Shells(None, topologies)
4796
- elif subTopologyClass == topologic.Cell:
4797
- _ = topology.Cells(None, topologies)
4798
- elif subTopologyClass == topologic.CellComplex:
4799
- _ = topology.CellComplexes(None, topologies)
4800
- return topologies
4801
4987
 
4802
4988
  vertices = []
4803
4989
  edges = []
@@ -4850,7 +5036,7 @@ class Topology():
4850
5036
  triFaces = Face.Triangulate(aFace)
4851
5037
  for aTriFace in triFaces:
4852
5038
  wire = Face.ExternalBoundary(aTriFace)
4853
- faceVertices = getSubTopologies(wire, topologic.Vertex)
5039
+ faceVertices = Topology.Vertices(wire)
4854
5040
  f = []
4855
5041
  for aVertex in faceVertices:
4856
5042
  try:
@@ -4862,7 +5048,7 @@ class Topology():
4862
5048
  faces.append(f)
4863
5049
  else:
4864
5050
  wire = Face.ExternalBoundary(aFace)
4865
- faceVertices = getSubTopologies(wire, topologic.Vertex)
5051
+ faceVertices = Topology.Vertices(wire)
4866
5052
  f = []
4867
5053
  for aVertex in faceVertices:
4868
5054
  try:
@@ -5121,7 +5307,6 @@ class Topology():
5121
5307
  The resulting merged Topology
5122
5308
 
5123
5309
  """
5124
-
5125
5310
  from topologicpy.Cluster import Cluster
5126
5311
 
5127
5312
  if not isinstance(topologies, list):
@@ -5207,6 +5392,7 @@ class Topology():
5207
5392
  The input topology.
5208
5393
  tolerance : float , optional
5209
5394
  The desired tolerance. The default is 0.0001.
5395
+
5210
5396
  Returns
5211
5397
  -------
5212
5398
  list
@@ -6302,7 +6488,6 @@ class Topology():
6302
6488
  A dictionary with the list of vertices, edges, wires, and faces. The keys are "vertices", "edges", "wires", and "faces".
6303
6489
 
6304
6490
  """
6305
-
6306
6491
  if not Topology.IsInstance(topologyA, "Topology"):
6307
6492
  print("Topology.SharedTopologies - Error: the input topologyA parameter is not a valid topology. Returning None.")
6308
6493
  return None
@@ -6429,9 +6614,10 @@ class Topology():
6429
6614
  l = None
6430
6615
  return l
6431
6616
 
6432
-
6433
6617
  @staticmethod
6434
6618
  def Show(*topologies,
6619
+ colorKey = "color",
6620
+ opacityKey = "opacity",
6435
6621
  showVertices=True, vertexSize=1.1, vertexColor="black",
6436
6622
  vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[],
6437
6623
  vertexMinGroup=None, vertexMaxGroup=None,
@@ -6466,7 +6652,10 @@ class Topology():
6466
6652
  ----------
6467
6653
  topologies : topologic_core.Topology or list
6468
6654
  The input topology. This must contain faces and or edges. If the input is a list, a cluster is first created
6469
-
6655
+ colorKey : str , optional
6656
+ The key under which to find the color of the topology. The default is "color".
6657
+ opacityKey : str , optional
6658
+ The key under which to find the opacity of the topology. The default is "opacity".
6470
6659
  showVertices : bool , optional
6471
6660
  If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
6472
6661
  vertexSize : float , optional
@@ -6630,10 +6819,11 @@ class Topology():
6630
6819
 
6631
6820
  """
6632
6821
 
6633
- from topologicpy.Cluster import Cluster
6822
+ from topologicpy.Dictionary import Dictionary
6634
6823
  from topologicpy.Plotly import Plotly
6635
6824
  from topologicpy.Helper import Helper
6636
6825
  from topologicpy.Graph import Graph
6826
+ from topologicpy.Color import Color
6637
6827
 
6638
6828
  if isinstance(topologies, tuple):
6639
6829
  topologies = Helper.Flatten(list(topologies))
@@ -6644,30 +6834,38 @@ class Topology():
6644
6834
  if len(new_topologies) == 0:
6645
6835
  print("Topology.Show - Error: the input topologies parameter does not contain any valid topology. Returning None.")
6646
6836
  return None
6647
- if len(new_topologies) == 1:
6648
- topology = new_topologies[0]
6649
- else:
6650
- topology = Cluster.ByTopologies(new_topologies)
6651
- if not Topology.IsInstance(topology, "Topology"):
6652
- print("Topology.Show - Error: the input topology parameter is not a valid topology. Returning None.")
6653
- return None
6654
- data = Plotly.DataByTopology(topology=topology,
6655
- showVertices=showVertices, vertexSize=vertexSize, vertexColor=vertexColor,
6656
- vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups,
6657
- vertexMinGroup=vertexMinGroup, vertexMaxGroup=vertexMaxGroup,
6658
- showVertexLegend=showVertexLegend, vertexLegendLabel=vertexLegendLabel, vertexLegendRank=vertexLegendRank,
6659
- vertexLegendGroup=vertexLegendGroup,
6660
- showEdges=showEdges, edgeWidth=edgeWidth, edgeColor=edgeColor,
6661
- edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups,
6662
- edgeMinGroup=edgeMinGroup, edgeMaxGroup=edgeMaxGroup,
6663
- showEdgeLegend=showEdgeLegend, edgeLegendLabel=edgeLegendLabel, edgeLegendRank=edgeLegendRank,
6664
- edgeLegendGroup=edgeLegendGroup,
6665
- showFaces=showFaces, faceOpacity=faceOpacity, faceColor=faceColor,
6666
- faceLabelKey=faceLabelKey, faceGroupKey=faceGroupKey, faceGroups=faceGroups,
6667
- faceMinGroup=faceMinGroup, faceMaxGroup=faceMaxGroup,
6668
- showFaceLegend=showFaceLegend, faceLegendLabel=faceLegendLabel, faceLegendRank=faceLegendRank,
6669
- faceLegendGroup=faceLegendGroup,
6670
- intensityKey=intensityKey, intensities=intensities, colorScale=colorScale, mantissa=mantissa, tolerance=tolerance)
6837
+ # if len(new_topologies) == 1:
6838
+ # topology = new_topologies[0]
6839
+ # else:
6840
+ # topology = Cluster.ByTopologies(new_topologies)
6841
+ # if not Topology.IsInstance(topology, "Topology"):
6842
+ # print("Topology.Show - Error: the input topology parameter is not a valid topology. Returning None.")
6843
+ # return None
6844
+ data = []
6845
+ for topology in new_topologies:
6846
+ d = Topology.Dictionary(topology)
6847
+ if isinstance(colorKey, str):
6848
+ f_color = Dictionary.ValueAtKey(d, colorKey)
6849
+ if f_color:
6850
+ faceColor = Color.PlotlyColor(f_color, alpha=1.0, useAlpha=False)
6851
+ faceOpacity = Dictionary.ValueAtKey(d, opacityKey) or faceOpacity
6852
+ data += Plotly.DataByTopology(topology=topology,
6853
+ showVertices=showVertices, vertexSize=vertexSize, vertexColor=vertexColor,
6854
+ vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups,
6855
+ vertexMinGroup=vertexMinGroup, vertexMaxGroup=vertexMaxGroup,
6856
+ showVertexLegend=showVertexLegend, vertexLegendLabel=vertexLegendLabel, vertexLegendRank=vertexLegendRank,
6857
+ vertexLegendGroup=vertexLegendGroup,
6858
+ showEdges=showEdges, edgeWidth=edgeWidth, edgeColor=edgeColor,
6859
+ edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups,
6860
+ edgeMinGroup=edgeMinGroup, edgeMaxGroup=edgeMaxGroup,
6861
+ showEdgeLegend=showEdgeLegend, edgeLegendLabel=edgeLegendLabel, edgeLegendRank=edgeLegendRank,
6862
+ edgeLegendGroup=edgeLegendGroup,
6863
+ showFaces=showFaces, faceOpacity=faceOpacity, faceColor=faceColor,
6864
+ faceLabelKey=faceLabelKey, faceGroupKey=faceGroupKey, faceGroups=faceGroups,
6865
+ faceMinGroup=faceMinGroup, faceMaxGroup=faceMaxGroup,
6866
+ showFaceLegend=showFaceLegend, faceLegendLabel=faceLegendLabel, faceLegendRank=faceLegendRank,
6867
+ faceLegendGroup=faceLegendGroup,
6868
+ intensityKey=intensityKey, intensities=intensities, colorScale=colorScale, mantissa=mantissa, tolerance=tolerance)
6671
6869
  figure = Plotly.FigureByData(data=data, width=width, height=height,
6672
6870
  xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize,
6673
6871
  backgroundColor=backgroundColor,
@@ -6700,8 +6898,8 @@ class Topology():
6700
6898
  A dictionary containing the list of sorted and unsorted topologies. The keys are "sorted" and "unsorted".
6701
6899
 
6702
6900
  """
6703
-
6704
6901
  from topologicpy.Vertex import Vertex
6902
+
6705
6903
  usedTopologies = []
6706
6904
  sortedTopologies = []
6707
6905
  unsortedTopologies = []
@@ -6925,6 +7123,7 @@ class Topology():
6925
7123
 
6926
7124
  """
6927
7125
  from topologicpy.Vertex import Vertex
7126
+
6928
7127
  ratioRange = [min(1,ratioRange[0]), min(1,ratioRange[1])]
6929
7128
  if ratioRange == [0, 0]:
6930
7129
  return topology
@@ -7263,7 +7462,6 @@ class Topology():
7263
7462
  The list of supertopologies connected to the input topology.
7264
7463
 
7265
7464
  """
7266
-
7267
7465
  if not Topology.IsInstance(topology, "Topology"):
7268
7466
  print("Topology.SuperTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
7269
7467
  return None
@@ -7394,7 +7592,6 @@ class Topology():
7394
7592
  The input topology with the dictionaries transferred to its subtopologies.
7395
7593
 
7396
7594
  """
7397
-
7398
7595
  if not Topology.IsInstance(topology, "Topology"):
7399
7596
  print("Topology.TransferDictionariesBySelectors - Error: The input topology parameter is not a valid topology. Returning None.")
7400
7597
  return None
@@ -7664,9 +7861,6 @@ class Topology():
7664
7861
  The type of the input topology.
7665
7862
 
7666
7863
  """
7667
- #if not Topology.IsInstance(topology, "Topology"):
7668
- #print("Topology.Type - Error: The input topology parameter is not a valid topology. Returning None.")
7669
- #return None
7670
7864
  return topology.Type()
7671
7865
 
7672
7866
  @staticmethod
@@ -7721,7 +7915,6 @@ class Topology():
7721
7915
  The type id of the input topologyType string.
7722
7916
 
7723
7917
  """
7724
-
7725
7918
  if not isinstance(name, str):
7726
7919
  print("Topology.TypeID - Error: The input topologyType parameter is not a valid string. Returning None.")
7727
7920
  return None