From f4169d9f9e537dfb806e591b0e4444d92edeb4da Mon Sep 17 00:00:00 2001 From: Wassim Jabi Date: Sun, 1 Dec 2024 16:34:51 +0000 Subject: [PATCH] Added PDF Import --- notebooks/PDF_Import.ipynb | 191 +++++++++++++++++++ notebooks/pdf_import_test.pdf | Bin 0 -> 3786 bytes src/topologicpy/Plotly.py | 18 +- src/topologicpy/Topology.py | 340 +++++++++++++++++++++++++++++++++- src/topologicpy/version.py | 2 +- 5 files changed, 540 insertions(+), 11 deletions(-) create mode 100644 notebooks/PDF_Import.ipynb create mode 100644 notebooks/pdf_import_test.pdf diff --git a/notebooks/PDF_Import.ipynb b/notebooks/PDF_Import.ipynb new file mode 100644 index 0000000..32917de --- /dev/null +++ b/notebooks/PDF_Import.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Your Topologic Version is: 0.7.78 . Make sure it is >= 0.7.78\n", + "Done. Imported 35 topologic entities.\n" + ] + } + ], + "source": [ + "# Import TopologicPy modules\n", + "import sys\n", + "sys.path.append(\"C:/Users/sarwj/OneDrive - Cardiff University/Documents/GitHub/topologicpy/src\")\n", + "\n", + "from topologicpy.Topology import Topology\n", + "from topologicpy.Helper import Helper\n", + "\n", + "print(\"Your Topologic Version is:\", Helper.Version(), \". Make sure it is >= 0.7.78\")\n", + "\n", + "# Insert Path to your PDF here\n", + "pdf_path = r\"C:\\Users\\sarwj\\OneDrive - Cardiff University\\Documents\\GitHub\\topologicpy\\notebooks\\pdf_import_test.pdf\"\n", + "\n", + "topologic_entities = Topology.ByPDFPath(pdf_path,\n", + " wires=False, # Not just edges, try to make wires\n", + " faces=True, # Not just edges, try to make faces\n", + " includeTypes = [\"line\", \"curve\", \"rectangle\", \"quadrilateral\"], # PDF path types to include\n", + " excludeTypes = [], # PDf path types to exclude\n", + " edgeColorKey=\"edge_color\", # The dictionary key under which to save the edge color\n", + " edgeWidthKey=\"edge_width\", # The dictionarykey under which to save the edge width\n", + " faceColorKey=\"face_color\", # The dictionarykey under which to save the face color\n", + " faceOpacityKey=\"face_opacity\", # The dictionary key under which to save the face opacity\n", + " tolerance=0.0001, # The desired tolerance\n", + " silent=False) # Set to True to suppress printing of error and warning messages\n", + "\n", + "print(\"Done. Imported\", len(topologic_entities),\"topologic entities.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Topology.Show(topologic_entities, showVertices=False, edgeWidthKey=\"edge_width\", edgeColorKey=\"edge_color\", faceColorKey=\"face_color\", faceOpacityKey=\"face_opacity\", camera=[0,0,10])" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n", + "['c', 'qu']\n" + ] + } + ], + "source": [ + "def map_types(type_list):\n", + " type_mapping = {\n", + " \"line\": \"l\",\n", + " \"curve\": \"c\",\n", + " \"rect\": \"re\",\n", + " \"quad\": \"qu\"\n", + " }\n", + " return [type_mapping[key] for key in type_mapping if any(key in item for item in type_list)]\n", + "\n", + "def remove_overlap(list1, list2):\n", + " set1, set2 = set(list1), set(list2)\n", + " # Remove common elements from both sets\n", + " set1 -= set2\n", + " set2 -= set(list1)\n", + " return list(set1), list(set2)\n", + "\n", + "#includeTypes = [\"rectangle\", \"line\", \"curve\", \"cu\", \"quad\"]\n", + "includeTypes = None\n", + "excludeTypes = [\"quadrilateral\", \"curve\", \"quad\"]\n", + "\n", + "\n", + "if includeTypes == None:\n", + " includeTypes = []\n", + "if excludeTypes == None:\n", + " excludeTypes = []\n", + "includeTypes = [item for item in includeTypes if isinstance(item, str)]\n", + "excludeTypes = [item for item in excludeTypes if isinstance(item, str)]\n", + "in_types = map_types([c.lower() for c in includeTypes])\n", + "ex_types = map_types([c.lower() for c in excludeTypes])\n", + "in_types_1, ex_types_1 = remove_overlap(in_types, ex_types)\n", + "if not len(in_types_1) == len(in_types) or not len(ex_types_1) == len(ex_types):\n", + " print(\"Topology.ByPDFFile - Warning: Ther includeTypes and excludeTypes input parameters contain overlapping elements. These have been excluded.\")\n", + "in_types = in_types_1\n", + "print(in_types)\n", + "print(ex_types)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/pdf_import_test.pdf b/notebooks/pdf_import_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f4b584815a83fe0a322f11306c0710c6814f47e GIT binary patch literal 3786 zcmb_f30M=?7H$zmqgX_WMG#|HWHFh{WFd(pLK0#lAR$6g1TiEN2xMzAlz^zOh)Y2b z6|D$W5K5_Ap=`1hYi+HH*jl9&eY{qAP%X#GinI6b0+B`Tbu_0axW-x=N(yBi#1E}G#|3rl8Oo;eY18fcnk8j)uWCI{xU>x3ze z_VJ4xTX2=h$h z7uKCBlJ{3?PaVi`N(tEctj5YEDfYKs!m8W3ZhfCzOLVj|i7G4c2>zxQdgn&sJL@-P z06CZ48y>X)3Xxu4c6X($LTVw;#cy)9ak}{0)_ET+$;)|-{doa5sVCs+Ja@}edv3Yp zmHDMoowo({9|(W*xK;R}?EK9G&ovS0vh8_C>X*&F6>#`b>zs$g!PfHA`*>G}SSCL@ zgtf2fbvGjqIuFT({oTADSh?Yc%LnJ@YW~sbop!%;>1C&@*?t$U&Y=qO|GoHubsBq^ z-8pDai0umwDXBFJ*EJ~a+kU((+5`uyWn;jjBP?H7mauOMosKPHL;6dpr2RoEr=~GH zxKy`f*}SeE_u}x)j$T~V;(^}f2FuzK+bHMkr}ZIr+T6}J2fibQmWLwy0_qb+D)NpjpcdzP0{frf;g4mSk6Gd9XFLZs~QisEm_!`R6vQ5NnFV94$v8iVrX< zSMK?$AuX%d@wRYvyP!sthVY0tb{;LXE$%8T{Jym*VU{{;VcA`bItpfWSN*hzTCLjrrM)pK{W{6jBFF7CFDEz&+0p=h;sHX zebDx^(Ljb8JmH#mEaCqv*7-{5FV*0_Z_^X)d!91(W>#4e?3ljiS*3XqhqSD@+u!ire{sti*OoF?KY;ir}3NF$MohEgo1a!TkM{e^1SLq z;MZ3DZb_-ti#T7`iFl2*EM(EbC2c9c-)j4!zgw20S^mS5_evT+6#38H&`W>qkm+J( zgvQLr;rp_s?KyDm`XdrW!|Oe}mDW}ncZ0*4GtWS>B1b2e)-_>6Li`ij>@!g8k<&zHbKznfproUyi;<10w9nKj3>JL^hVMgKpq zg3aO+TL$h3{d&8xsxT4$H5lur$qTxG7cgkk#i}FU& z``)<34F9IaDq@jsY~`IF6TbLlc%-WT=;{i$X8(=((%*;~;uwx}kiNe_Th&radgk`1 zKk;Kl%!+7HulxPO4!k@r7YV(l5?t%vLvOb%ODGV&TS|0DZ(R6QR!L&Oyw#5>HCvC? zuG#SfU);I8G&x`fRGc}SrF}NjTI9E&`|L8iUB77CBfHx^=`wliVYKxFGp{$Z*c$Nx z;lRvax1IdY%M8QFgVD;v!(*Ib;5h40wM29htrnKK$R$cW>rhpNNHh}76$nxwO4tQ~^#~avX=U;;Q-wy&N2S(<&w|Jh6$BwB z2vTVfGt?Cj=`^x9DXeorm0PKZbQkf3c#up&KU$Eez=yenDAW`v%+(d8^_UcZ)R(jX z#FSnP1SM!;z#m2=G6^C9*2P64*u;8&ze!E-TxHNoq_Hr9kA$NXYBr&#wt;|G$k>Ds znm^fJ%Y&m8Zzt;Db%|?(q=~Uori`$PgTteLo(7gdrB$Kc@dlMrt!Ek71PQ85Fbf@H z(*R*~Bf3>5le5Hp!PrLh7n=}`AX*jx#K*^z;wdDJE(!pdOeR2v00<(YJBa!OH6k$( z)p~~sKVzOyoTWO27C~XeW1c0EnmB|_AdJVM()#;PJV31{8KWXeH7dX$(E=ce45(C- z!HvEPX2TMy^@v0*g@t@hT%1D2VlwGG27|&SdiYT2L>`|G5$OyjnMfz|xD1fb1HA=& zPXI+>x~&l&%q<3zagnc)#;IU6GVT~;@Oc6Ok4I$q(7lOdh(RL?e7IDi4@JP|(D548!l~Ggh;{;tSm(uugV zaXO_@4P;UPR>Bx*^k`_{3t-BmECkcm$+|bP4P%A35}7_b#tEeY(+8GPqK;w{3`7|$ zm&7R%0%ua4N9#eUm~K8g`+`?b;N=MZu$)F0Q2iTqjF$Gea&mB!`irVLo1nwg6&+1K z{~6^RYfm5*n(R;GJqztmMxp1SBCpg$0U}8htjB}K4lrK$cw>_gPiY$1I_7fnE&yU@ z%2!?njP3&>W7Z)m^kP6q?+ZFTf~Qa?+lZl6CHgYvEAqVhVfWu>R@^%0Z{oDu;Gm3c z&g`;IB&V~kaP1<^Y8s9&F}a<`+A~l%V9#~&DtSvRo3q(*1Mb|1ekMf=crDJ~&Nn;j zrzw)(T-4Y8!P&%z;ohU+$*1Z<-`Ju-8)y$zMNW_IVnk4a!(aj!B$xn2y*fslaf#Ih^sUA%kMio^Nv6Qzz->Ar)e5bjW2lT~1IbkfD&N4B^1Rc;B%Smx(z=m2{1 iJ2LI<%kwOYUPcw|!8*jCgXK61l}f=mIr#(#aQ_8VoOAmC literal 0 HcmV?d00001 diff --git a/src/topologicpy/Plotly.py b/src/topologicpy/Plotly.py index 68c56a8..d29b136 100644 --- a/src/topologicpy/Plotly.py +++ b/src/topologicpy/Plotly.py @@ -599,13 +599,17 @@ def edgeData(vertices, edges, dictionaries=None, color="black", colorKey=None, w else: minGroup = 0 maxGroup = 1 + if colorKey or widthKey or labelKey or groupKey: keys = [x for x in [colorKey, widthKey, labelKey, groupKey] if not x == None] temp_dict = Helper.ClusterByKeys(edges, dictionaries, keys, silent=False) dict_clusters = temp_dict["dictionaries"] elements_clusters = temp_dict['elements'] + n = len(str(len(elements_clusters))) + labels = [] for j, elements_cluster in enumerate(elements_clusters): + labels.append("Edge_"+str(j+1).zfill(n)) d = dict_clusters[j][0] # All dicitonaries have same values in dictionaries, so take first one. if d: if not colorKey == None: @@ -629,6 +633,7 @@ def edgeData(vertices, edges, dictionaries=None, color="black", colorKey=None, w else: d_color = Color.ByValueInRange(groups.index(group), minValue=minGroup, maxValue=maxGroup, colorScale=colorScale) color = d_color + x = [] y = [] z = [] @@ -639,15 +644,19 @@ def edgeData(vertices, edges, dictionaries=None, color="black", colorKey=None, w y+=[sv[1], ev[1], None] # y-coordinates of edge ends z+=[sv[2], ev[2], None] # z-coordinates of edge ends if showEdgeLabel == True: - mode = "lines+text" + mode = "markers+lines+text" else: - mode = "lines" + mode = "markers+lines" + if isinstance(width, list): + marker_width = width[0]*0.25 + else: + marker_width = width*0.25 trace = go.Scatter3d(x=x, y=y, z=z, name=label, showlegend=showLegend, - marker_size=0, + marker=dict(symbol="circle", size=marker_width), mode=mode, line=dict(color=color, width=width), legendgroup=legendGroup, @@ -894,7 +903,7 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", colorKey=None, label = "" group = None groupList.append(Color.AnyToHex(color)) # Store a default color for that face - labels.append("Face_"+str(m+1).zfill(n)) + labels.append("Mace_"+str(m+1).zfill(n)) if len(dictionaries) > 0: d = dictionaries[m] if d: @@ -1033,6 +1042,7 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", colorKey=None, if edgeColorKey or edgeWidthKey or edgeLabelKey or edgeGroupKey: for tp_edge in tp_edges: e_dictionaries.append(Topology.Dictionary(tp_edge)) + e_cluster = Cluster.ByTopologies(tp_edges) geo = Topology.Geometry(e_cluster, mantissa=mantissa) vertices = geo['vertices'] diff --git a/src/topologicpy/Topology.py b/src/topologicpy/Topology.py index 852a09f..d09b4d2 100644 --- a/src/topologicpy/Topology.py +++ b/src/topologicpy/Topology.py @@ -1509,7 +1509,9 @@ def ByGeometry(vertices=[], edges=[], faces=[], topologyType: str = None, tolera faces : list , optional The input list of faces in the form of [i, j, k, l, ...] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically. topologyType : str , optional - The desired topology type. The options are: "Vertex", "Edge", "Wire", "Face", "Shell", "Cell", "CellComplex". If set to None, a "Cluster" will be returned. The default is None. + The desired highest topology type. The options are: "Vertex", "Edge", "Wire", "Face", "Shell", "Cell", "CellComplex". + It is case insensitive. If any of these options are selected, the returned topology will only contain this type either a single topology + or as a Cluster of these types of topologies. If set to None, a "Cluster" will be returned of vertices, edges, and/or faces. The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional @@ -1555,7 +1557,7 @@ def topologyByFaces(faces, topologyType, tolerance): output = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list if Topology.IsInstance(output, "Shell"): return output - elif topologyType == None: + elif topologyType == None or topologyType== "face": output = Cluster.ByTopologies(faces) return output @@ -1587,15 +1589,20 @@ def topologyByEdges(edges, topologyType): topologyType = topologyType.lower() if topologyType == "vertex": - if len(topVerts) >= 1: + if len(topVerts) == 0: + return None + if len(topVerts) == 1: return topVerts[0] else: - return None + return Cluster.ByTopologies(topVerts) elif topologyType == "edge": - if len(edges) >= 1 and len(vertices) >= 2: + if len(edges) == 0: + return None + if len(edges) == 1 and len(vertices) >= 2: return Edge.ByVertices(topVerts[edges[0][0]], topVerts[edges[0][1]], tolerance=tolerance) else: - return None + topEdges = [Edge.ByVertices([topVerts[e[0]], topVerts[e[1]]], tolerance=tolerance) for e in edges] + return Cluster.ByTopologies(topEdges) if topologyType == "wire" and edges: topEdges = [Edge.ByVertices([topVerts[e[0]], topVerts[e[1]]], tolerance=tolerance) for e in edges] @@ -3635,6 +3642,325 @@ def ByOCCTShape(occtShape): """ return topologic.Topology.ByOcctShape(occtShape, "") + @staticmethod + def ByPDFFile(file, wires=False, faces=False, includeTypes=[], excludeTypes=[], edgeColorKey="edge_color", edgeWidthKey="edge_width", faceColorKey="face_color", faceOpacityKey="face_opacity", tolerance=0.0001, silent=False): + """ + Import PDF file and convert its entities to topologies. + + Parameters + ---------- + file : file + The input PDF file + wires : bool , optional + If set to True, wires will be constructed when possible. The default is True. + faces : bool , optional + If set to True, faces will be constructed when possible. The default is True. + includeTypes : list , optional + A list of PDF object types to include in the returned result. The default is [] which means all PDF objects will be included. + The possible strings to include in this list are: ["line", "curve", "rectangle", "quadrilateral"] + excludeTypes : list , optional + A list of PDF object types to exclude from the returned result. The default is [] which mean no PDF object type will be excluded. + The possible strings to include in this list are: ["line", "curve", "rectangle", "quadrilateral"] + edgeColorKey : str , optional + The dictionary key under which to store the edge color. The default is None. + edgeWidthKey : str , optional + The dictionary key under which to store the edge width. The default is None. + faceColorKey : str , optional + The dictionary key under which to store the face color. The default is None. + faceOpacityKey : str , optional + The dictionary key under which to store the face opacity. The default is None. + tolerance : float , optional + The desired tolerance. The default is 0.0001. + silent : bool , optional + If set to True, no error and warning messages are printed. Otherwise, they are. The default is False. + + Returns + ------- + list + A list of Topologic entities (edges, wires, faces, clusters) with attached dictionaries. + + """ + from topologicpy.Vertex import Vertex + from topologicpy.Edge import Edge + from topologicpy.Wire import Wire + from topologicpy.Face import Face + from topologicpy.Cluster import Cluster + from topologicpy.Dictionary import Dictionary + import os + import warnings + + def interpolate_bezier(control_points, num_points=50): + """ + Interpolate a cubic Bezier curve given control points. + + Args: + control_points (list): List of control points (pymupdf.Point objects). + num_points (int): Number of points to interpolate along the curve. + + Returns: + list: A list of interpolated points (pymupdf.Point objects). + """ + p0, p1, p2, p3 = control_points + points = [ + pymupdf.Point( + (1 - t)**3 * p0.x + 3 * (1 - t)**2 * t * p1.x + 3 * (1 - t) * t**2 * p2.x + t**3 * p3.x, + (1 - t)**3 * p0.y + 3 * (1 - t)**2 * t * p1.y + 3 * (1 - t) * t**2 * p2.y + t**3 * p3.y + ) + for t in (i / num_points for i in range(num_points + 1)) + ] + return points + + def map_types(type_list): + type_mapping = { + "line": "l", + "curve": "c", + "rect": "re", + "quad": "qu" + } + return [type_mapping[key] for key in type_mapping if any(key in item for item in type_list)] + + def remove_overlap(list1, list2): + set1, set2 = set(list1), set(list2) + # Remove common elements from both sets + set1 -= set2 + set2 -= set(list1) + return list(set1), list(set2) + + try: + import pymupdf # PyMuPDF + except: + if not silent: + print("Topology.ByPDFFile - Warning: Installing required PyMuPDF library.") + try: + os.system("pip install PyMuPDF") + except: + os.system("pip install PyMuPDF --user") + try: + import pymupdf + if not silent: + print("Topology.ByPDFFile - Information: PyMUDF library installed correctly.") + except: + if not silent: + warnings.warn("Topology.ByPDFFile - Error: Could not import PyMuPDF. Please try to install PyMuPDF manually. Returning None.") + return None + if not file: + if not silent: + print("Topology.ByPDFFile - Error: Could not open the PDF file. Returning None.") + return None + + if includeTypes == None: + includeTypes = [] + if excludeTypes == None: + excludeTypes = [] + includeTypes = [item for item in includeTypes if isinstance(item, str)] + excludeTypes = [item for item in excludeTypes if isinstance(item, str)] + in_types = map_types([c.lower() for c in includeTypes]) + ex_types = map_types([c.lower() for c in excludeTypes]) + in_types_1, ex_types_1 = remove_overlap(in_types, ex_types) + if not len(in_types_1) == len(in_types) or not len(ex_types_1) == len(ex_types): + if not silent: + print("Topology.ByPDFFile - Warning: Ther includeTypes and excludeTypes input parameters contain overlapping elements. These have been excluded.") + in_types = in_types_1 + + topologic_entities = [] + for page_num, page in enumerate(file, 1): + matrix = pymupdf.Matrix(1, 1) # Identity matrix for default transformation + paths = page.get_drawings() + for path in paths: + if not path.get("stroke_opacity") == 0: + close = path.get("closePath") + components = [] + edge_color = path.get("color", [0, 0, 0]) or [0, 0, 0] + edge_color = [int(255 * c) for c in edge_color] # Convert stroke color to 0-255 range + edge_width = path.get("width", 1) or 1 + face_color = path.get("fill", [1, 1, 1]) or [1,1,1] + face_color = [int(255 * c) for c in face_color] + face_opacity = path.get("fill_opacity", 1) or 1 + + # Create the dictionary for line width, color, fill color, and fill opacity + keys = [edgeWidthKey, edgeColorKey, faceColorKey, faceOpacityKey] + values = [ + edge_width, + edge_color, # Convert stroke color to 0-255 range + face_color, # Convert fill color to 0-255 range + face_opacity + ] + dictionary = Dictionary.ByKeysValues(keys, values) + items = path.get("items") or [] + for it, item in enumerate(items): + if (item[0] in in_types or len(in_types) == 0) and (not item[0] in ex_types): + if item[0] == "l": # Line + start_point = pymupdf.Point(item[1][0], item[1][1]).transform(matrix) + end_point = pymupdf.Point(item[2][0], item[2][1]).transform(matrix) + start_vertex = Vertex.ByCoordinates(start_point.x, -start_point.y, 0) + end_vertex = Vertex.ByCoordinates(end_point.x, -end_point.y, 0) + d = Vertex.Distance(start_vertex, end_vertex) + if d > tolerance: + edge = Edge.ByStartVertexEndVertex(start_vertex, end_vertex, tolerance=tolerance, silent=silent) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + if it == 0: + v_f_p = pymupdf.Point(item[1][0], item[1][1]).transform(matrix) + very_first_vertex = Vertex.ByCoordinates(v_f_p.x, -v_f_p.y, 0) + if it == len(items)-1 and path.get("closePath", False) == True: + v_l_p = pymupdf.Point(item[2][0], item[2][1]).transform(matrix) + very_last_vertex = Vertex.ByCoordinates(v_l_p.x, -v_l_p.y, 0) + edge = Edge.ByStartVertexEndVertex(very_last_vertex, very_first_vertex, tolerance=tolerance, silent=True) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + + elif item[0] == "c": # Bezier curve (approximated by segments) + control_points = [pymupdf.Point(p[0], p[1]).transform(matrix) for p in item[1:]] + bezier_points = interpolate_bezier(control_points) + vertices = [Vertex.ByCoordinates(pt.x, -pt.y, 0) for pt in bezier_points] + for i in range(len(vertices)-1): + start_vertex = vertices[i] + end_vertex = vertices[i+1] + edge = Edge.ByStartVertexEndVertex(start_vertex, end_vertex, tolerance=tolerance, silent=False) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + elif item[0] == "re": # Rectangle + x0, y0, x1, y1 = item[1] + vertices = [ + Vertex.ByCoordinates(x0, -y0, 0), + Vertex.ByCoordinates(x1, -y0, 0), + Vertex.ByCoordinates(x1, -y1, 0), + Vertex.ByCoordinates(x0, -y1, 0) + ] + for i in range(len(vertices)-1): + start_vertex = vertices[i] + end_vertex = vertices[i+1] + edge = Edge.ByStartVertexEndVertex(start_vertex, end_vertex, tolerance=tolerance, silent=False) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + edge = Edge.ByStartVertexEndVertex(vertices[-1], vertices[0], tolerance=tolerance, silent=False) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + + elif item[0] == "qu": # Quadrilateral + quad_points = [pymupdf.Point(pt[0], pt[1]).transform(matrix) for pt in item[1]] + vertices = [Vertex.ByCoordinates(pt.x, -pt.y, 0) for pt in quad_points] + for i in range(len(vertices)-1): + start_vertex = vertices[i] + end_vertex = vertices[i+1] + edge = Edge.ByStartVertexEndVertex(start_vertex, end_vertex, tolerance=tolerance, silent=False) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + edge = Edge.ByStartVertexEndVertex(vertices[-1], vertices[0], tolerance=tolerance, silent=False) + if not edge == None: + edge = Topology.SetDictionary(edge, dictionary) # Set dictionary + components.append(edge) + + if len(components) > 0: + if len(components) == 1: + tp_object = components[0] + elif len(components) > 1: + tp_object = Cluster.ByTopologies(components) + if wires == True or faces == True: + tp_object = Topology.SelfMerge(tp_object) + if faces == True: + if Topology.IsInstance(tp_object, "wire"): + if Wire.IsClosed(tp_object): + tp_object = Face.ByWire(tp_object, silent=True) or tp_object + tp_object = Topology.SetDictionary(tp_object, dictionary) + edges = Topology.Edges(tp_object) + for edge in edges: + edge = Topology.SetDictionary(edge, dictionary) + topologic_entities.append(tp_object) + + return topologic_entities + + @staticmethod + def ByPDFPath(path, wires=False, faces=False, includeTypes=[], excludeTypes=[], edgeColorKey="edge_color", edgeWidthKey="edge_width", faceColorKey="face_color", faceOpacityKey="face_opacity", tolerance=0.0001, silent=False): + """ + Import PDF file and convert its entities to topologies. + + Parameters + ---------- + path : path + The input path to the PDF file + wires : bool , optional + If set to True, wires will be constructed when possible. The default is True. + faces : bool , optional + If set to True, faces will be constructed when possible. The default is True. + includeTypes : list , optional + A list of PDF object types to include in the returned result. The default is [] which means all PDF objects will be included. + The possible strings to include in this list are: ["line", "curve", "rectangle", "quadrilateral"] + excludeTypes : list , optional + A list of PDF object types to exclude from the returned result. The default is [] which mean no PDF object type will be excluded. + The possible strings to include in this list are: ["line", "curve", "rectangle", "quadrilateral"] + edgeColorKey : str , optional + The dictionary key under which to store the edge color. The default is None. + edgeWidthKey : str , optional + The dictionary key under which to store the edge width. The default is None. + faceColorKey : str , optional + The dictionary key under which to store the face color. The default is None. + faceOpacityKey : str , optional + The dictionary key under which to store the face opacity. The default is None. + tolerance : float , optional + The desired tolerance. The default is 0.0001. + silent : bool , optional + If set to True, no error and warning messages are printed. Otherwise, they are. The default is False. + + Returns + ------- + list + A list of Topologic entities (edges, wires, faces, clusters) with attached dictionaries. + + """ + import os + import warnings + try: + import pymupdf # PyMuPDF + except: + if not silent: + print("Topology.ByPDFPath - Warning: Installing required PyMuPDF library.") + try: + os.system("pip install PyMuPDF") + except: + os.system("pip install PyMuPDF --user") + try: + import pymupdf + if not silent: + print("Topology.ByPDFPath - Information: PyMUDF library installed correctly.") + except: + if not silent: + warnings.warn("Topology.ByPDFPath - Error: Could not import PyMuPDF. Please try to install PyMuPDF manually. Returning None.") + return None + if not isinstance(path, str): + if not silent: + print("Topology.ByPDFPath - Error: the input path is not a valid path. Returning None.") + return None + if not os.path.exists(path): + if not silent: + print("Topology.ByPDFPath - Error: The specified path does not exist. Returning None.") + return None + pdf_file = pymupdf.open(path) + if not pdf_file: + if not silent: + print("Topology.ByPDFPath - Error: Could not open the PDF file. Returning None.") + return None + + topologies = Topology.ByPDFFile(file=pdf_file, + wires=wires, + faces=faces, + includeTypes = includeTypes, + excludeTypes = excludeTypes, + edgeColorKey=edgeColorKey, + edgeWidthKey=edgeWidthKey, + faceColorKey=faceColorKey, + faceOpacityKey=faceOpacityKey, + tolerance=tolerance, + silent=silent) + pdf_file.close() + return topologies + @staticmethod def ByBREPString(string): """ @@ -7697,6 +8023,8 @@ def Show(*topologies, if not silent: print("Topology.Show - Error: the input topologies parameter does not contain any valid topology. Returning None.") return None + if camera[0] == 0 and camera[1] == 0 and up == [0,0,1]: + up = [0,1,0] #default to positive Y axis being up if looking down or up at the XY plane data = [] for topology in new_topologies: if Topology.IsInstance(topology, "Graph"): diff --git a/src/topologicpy/version.py b/src/topologicpy/version.py index 63b6943..43a6d1b 100644 --- a/src/topologicpy/version.py +++ b/src/topologicpy/version.py @@ -1 +1 @@ -__version__ = '0.7.77' +__version__ = '0.7.78'