Jump to content

Recommended Posts

Posted

Hello Autolisp world

 

I m an engineer and dealing with hydraulics alot. I have a hydraulic calculation software that accepts numbers as nodes incrementally in an common text editor. Every node however is unique in space connecting 2 pipes (e.g. through an elbow) or 3 pipes (e.g tee). The numbers are arbitrary. For instance

 

1 2 4 12U

2 3 4 5N

3 4 4 5E

4 5 4 5S

5 2 4 5W

 

The above creates a square of 5 ft side made of of 4" pipes feeded by a riser of 4" and 12 ft high. The first two numbers for every line represent the start and end node. The next the nominal diameter of pipe and the last the length along with the orientation of it.

 

 

I have made so far an autolisp routine that writes in a txt file lines like the above per every line sketched in Autocad representing accurately the pipe network using a while loop and a counter. I retrieve the coordinates of start and end points (x,y,z) using handles 10 & 11 and I concatenate the numbers so that I can make them unique in space. When one pipe has coordinates in space starting point 50,35,12 & endpoint 70,35,12 the number for the start & end point formed is 503512 & 703512 respectively. So my program writes in a new line

 

503512 703512 4 20E

 

This pipe has starting node 503512, end node 703512, 4" size and runs 20 ft to the east (taking as given that positive y is north). Repeatedly this is done for all lines in the pipe network.

 

Here begins my problem. My hydraulic software does not accept node numbers greater than 99999. So this means that I need to translate all of my points in the format xxyyzz to something less than 99999. No matter what it is but it has to be between 1 to 99999. It also has to be unique because on that node other pipes may be connected.

 

I haven't been able to to so. I need a function I guess that gives me these unique converted values or something like a renumbering routine but I couldn't figure it out. Maybe my whole approach is not correct. Any help will be deeply appreciated.

  • Replies 21
  • Created
  • Last Reply

Top Posters In This Topic

  • MJLM

    10

  • ymg3

    8

  • Hippe013

    2

  • pBe

    1

Top Posters In This Topic

Posted Images

Posted

After some research it turned out that my approach was indeed wrong. I solved the problem by creating a point on any line intersection thus having a handle on every node. That gave me what I wanted.

 

I have an other question hoping that somebody provide some help. I have made lines and then I realize I need to create more to modify my piping system. Problem is, these new lines always get the next available handle (5 . "...") thus coming at last in the file where I type all data. That makes the system crash because these new nodes (coming from the modification) should have been prior to some others for proper regeneration and not at the end.

 

So, Is it possible to change the sequence of handles in the drawing database so that all get proper sequential renumbering?

Posted

Your routine interest me MLJM.

 

Consider using XDATA instead of the cumbersome DXF group code 5 [Handle]

Posted

yeap, I tried that too. Problem is I need to figure out a way to import on every pipe line the XDATA based on geometry and not on any other handle to avoid the sequence created by the (5 . handle). That means I need to make a while loop so that it can understand the x,y,z start & end of every line and include XDATA for the new line that comes inbetween the existing. That would make every line have an increment number based on geometry and not on the internal sequence autocad creates. On tee junctions that can be tricky as there lie three lines on a common node.

 

Unfortunately I haven't been able so far. I ll keep looking. If anyone has an idea I would happy to hear it.

Posted

I suppose I would look into using data extension functions. Same as what is used in GIS. You can then customize what ever fields or attributes you'd like and export to a dbx file which is opened with excel. acmaplisp.chm is a good resource. As pBe said I would avoid using Handles.

 

I hope that I am understanding what it is that you are looking for.

 

regards,

 

Hippe013

Posted

Understood. My problem is not the data (handles or Xdata). My problem is that am practically scanning a complete network of interconnected lines (representing piping network) through a while loop. The sequence of that loop is always the order of line creation (based always on handle). I cannot change that unfortunately and that's what creating my problem. If I edit a segment in between the start and end of the piping network then the line I am getting on my txt file for that segment comes at last and the network will collapse simply because this pipe is missing in the sequence.

 

In two words, it all about controlling the order a while loop works for entities.

 

Btw, would it be possible to have data instead of clipboard or txt file pasted in excel? You mentioned something above but I m not sure I got it.

Posted

Why don't you post your routine so that we can help you with it?

Posted

It is not a matter of routine. It s a matter of how my other software handles the node order. Every starting node of a new pipe segment in the list must be absolutely given already at least once above it as a ending node. If not it crushes. The problem is that in Autocad I need to make changes to the design in various locations. These changes bring new piping hence new segements that are created after all others regardless if geometrically are placed inbetween the whole pipe network and connected with other segmetns. These will always come at the bottom of the list generated by a while loop for all lines (ssget "X" '((0 . "LINE"))) simply because they came later in time. But the other software reports error because these nodes are not yet defined so that the older segments drawn and connected to these latest can be drawn.

 

I m not sure if it is understood. The only work around I found is to do cut & paste the older segments but geometrically after the modifications (running down the network) within the same drawing. This way these older segments take a new handle therefore they appear as created later that the new added segments. This way it works but one should be very careful what is drawn earlier or after a change/addition. Not the best thing but at least it works fine.

Posted

MJLM,

 

If you make a point list out of the endpoint of each line, not allowing any repetition of points.

 

The position in the list of any given endpoint would be the number of the nodes.

 

ymg

Posted

MJLM,

 

A bit of code to show you what I meant.

 

Note that the sorting is not necessary, but could be useful.

 

(defun c:test ()
 (prompt "\nSelect Network : ")   
 (setq s (ssget '((0 . "LINE"))))
  
 (setq pl nil)
 (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
            p (cdr (assoc 10 ent))
           pl (if (not (member p pl)) (cons p pl) pl)
            p (cdr (assoc 11 ent))
           pl (if (not (member p pl)) (cons p pl) pl)                                        
     )
  )                
  (setq pl (vl-sort pl (function (lambda (a b) (< (caddr a) (caddr b)))))	;; Sort on Z Coordinate     
        pl (vl-sort pl (function (lambda (a b) (< (cadr  a) (cadr  b)))))	;; Sort on Y Coordinate     
        pl (vl-sort pl (function (lambda (a b) (< (car   a) (car   b)))))      ;; Sort on X Coordinate     
  )
  (foreach p pl
     (mk_mtext p (itoa (vl-position p pl)) 5 2 0)
  )
  (princ)
)          





;;****************************************************************************;
;; mk_mtext                                                                   ;
;; Arguments: p,  Insertion Point.                                            ;
;;            s,  Text.                                                       ;
;;            j,  Justification:                                              ;
;;                1 = Top left; 2 = Top center; 3 = Top right;                ;
;;                4 = Middle left; 5 = Middle center; 6 = Middle right;       ;
;;                7 = Bottom left; 8 = Bottom center; 9 = Bottom right        ;
;;            h,  Text Height.                                                ;
;;            r,  Rotation.                                                   ;
;;****************************************************************************;

(defun mk_mtext (p s j h r)
  (entmakex
     (list (cons 0   "MTEXT")         
           (cons 100 "AcDbEntity")          
           (cons 100 "AcDbMText")    
           (cons 10 p)
           (cons 71 j)
           (cons 40 h)
           (cons 50 r)
           (cons  1 s)
     )
  )
)               

Posted

I m trying to take advantage of your code but it seems that I cannot select the mtext entity and retrieve its text.

After execution of your code, I am using (ssget (list (cons 0 "MTEXT") (list 10 5 5 10))) to pick a text that lies on the intersection of two lines at 5,5,10. The text has the property of (10 5 5 10) but it gives me 0 entities in the selection set. I guess it has to do with the fact that virtually no entity passes through the point of 5,5,10. How else can I select it automatically and retrieve its text?

Posted (edited)

MJML,

 

Why would you do that ?

 

You have the point list. Only reason I created "MTEXT" was to illustrate what was possible.

 

You definitively should work with the point list, more specifically with the VL-POSITION of the end point

of your lines in the list.

 

(defun c:test ()
 (prompt "\nSelect Network : ")   
 (setq s (ssget '((0 . "LINE"))))
  
 (setq pl nil el nil)
 (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
           p1 (cdr (assoc 10 ent))
           pl (if (not (member p1 pl)) (cons p1 pl) pl)
           p2 (cdr (assoc 11 ent))
           pl (if (not (member p2 pl)) (cons p2 pl) pl)
           el (cons (list (vl-position p1 pl) (vl-position p2 pl)
     )
  )              

 

In the above snippet, we construct yet another list el which is your line list.

 

She looks something like ((0 1) (1 2) (2 3)...), what you need to do is to scan that list

repeatedly until you've aligned endpoint to start point.

if necessary. Ex: (0 1) (2 1), you reverse the (2 1)

 

ymg

Edited by ymg3
Posted

I am not sure I m following you fully. Needless to say I m not an Autolisp expert. My intent is not to have list of all nodes. My intent is as explained in my first post. An ascii character file with every line having start node, end node, pipe size, length & orientation. I ve managed to put all of it in a txt file but the order cannot be arbitrary. Every start node must have been given earlier (above in the list) otherwise the software gives error. See below this imaginary pipe system

 

Drawing11.jpg

 

Assuming that all pipe segments have a common diameter of 4" and a length of 5ft, and they are created in the order 0-1, 1-3, 3-4, 4-2, 2-1, the correct way to write this is

 

0 1 4 5U

1 3 4 5E

3 4 4 5N

4 2 4 5W

2 1 4 5S

 

As seen above the numbers need not to be an incremental pattern. Except the first line, all starting nodes are defined as ending nodes somewhere above, no matter where. It can be 1 line above or 1000 lines above.

 

All of this I have already made using basically a ssget "X" '((0 . "LINE")) selection and a while loop for all lines. Therefore I can retrieve the pipe size, length and orientation plus the nodes. However if I delete the segment 3 to 4 and make it like the following due to an obstruction in the building:

 

Drawing2.jpg

 

my routine will be executed based on the creation order of all lines. That means that the five new segments between 3 & 4 will be respected as the last five. Therefore the list I ll get will be:

 

0 1 4 5U

1 3 4 5E

6 2 4 5W

2 1 4 5S

3 4 4 1.5N

4 7 4 1.5E

7 8 4 1.5N

8 5 4 1.5W

5 6 4 2N

 

The following gives an error simply because node 6 is not given above the third line (6 2). And this is what I am trying to solve.

 

 

I m sorry I m repeating myself but these are deep waters for me in Autolisp.

Posted

MJLM,

 

It's a little late so I did not read your post in detail bu will tomorrow.

 

However my intervention up to now is so you can build a list of numbered vertex

respecting the constraint you gave. I did not look into giving the diirection and/or distance

only vertex numbers.

 

Have a try of this code, It will normally give a list integers list following your network.

 

If you have antenna, that is not everything loops on itself, there will one list of integers per branch.

 

(defun c:test ()
 (prompt "\nSelect Network : ")   
 (setq s (ssget '((0 . "LINE"))))
 ; Build Point list               
 (setq pl nil) 
 (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
           p1 (cdr (assoc 10 ent))
           pl (if (not (vl-position p1 pl)) (cons p1 pl) pl)
           p2 (cdr (assoc 11 ent))
           pl (if (not (vl-position p2 pl)) (cons p2 pl) pl)
    )
  )
  ; Draw Vertex Number
  (foreach p pl
     (mk_mtext p (itoa (vl-position p pl)) 5 2 0)
  )

  ; Build the Edge List
  (setq el nil)
  (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
           p1 (cdr (assoc 10 ent))
           el (cons (vl-position p1 pl) el)
           p2 (cdr (assoc 11 ent))
           el (cons (vl-position p2 pl) el)            
     )
  )                
  ;(setq pl (vl-sort pl (function (lambda (a b) (< (caddr a) (caddr b)))))	;; Sort on Z Coordinate     
  ;      pl (vl-sort pl (function (lambda (a b) (< (cadr  a) (cadr  b)))))	;; Sort on Y Coordinate     
  ;      pl (vl-sort pl (function (lambda (a b) (< (car   a) (car   b)))))      ;; Sort on X Coordinate     
  

  (setq xl nil)      
    (print el)
    (while el                                          
      (setq pol (list (cadr el)(car el))        ; We go reverse as we will cons the polyline             
            nxt (car pol)                       ; nxt will be our next edge                              
      el (cddr el)                       ; Remove first two edges from el                         
      )
      (if (not (vl-position nxt el))            ;The previous edge was an end of line                    
    (setq pol (reverse pol)              ;We reverse our Polyline                                 
	  nxt (car pol)                  ;and adjust our next edge                                
           )
      )
      
      (while (setq n (vl-position nxt el)) 	 
          (setq  el (removenth n el)
                  n (- n (rem n 2))
                pol (cons (nth n el) pol)
	  el (removenth n el)
                nxt (car pol) 
   )
   	 
   (if (not (vl-position nxt el))
      (setq pol (reverse pol)
            nxt (car pol)
      )
   )  
      )
      
      (setq xl (cons pol xl))
      
    )
    (princ xl)
    (princ)
)     




;;****************************************************************************;
;; mk_mtext                                                                   ;
;; Arguments: p,  Insertion Point.                                            ;
;;            s,  Text.                                                       ;
;;            j,  Justification:                                              ;
;;                1 = Top left; 2 = Top center; 3 = Top right;                ;
;;                4 = Middle left; 5 = Middle center; 6 = Middle right;       ;
;;                7 = Bottom left; 8 = Bottom center; 9 = Bottom right        ;
;;            h,  Text Height.                                                ;
;;            r,  Rotation.                                                   ;
;;****************************************************************************;

(defun mk_mtext (p s j h r)
  (entmakex
     (list (cons 0   "MTEXT")         
           (cons 100 "AcDbEntity")          
           (cons 100 "AcDbMText")    
           (cons 10 p)
           (cons 71 j)
           (cons 40 h)
           (cons 50 r)
           (cons  1 s)
     )
  )
)

;;----------------------=={ Remove Nth }==--------------------;;
;;                                                            ;;
;;  Removes the item at the nth index in a supplied list      ;;
;;------------------------------------------------------------;;
;;  Author: Lee Mac, Copyright © 2011 - www.lee-mac.com       ;;
;;------------------------------------------------------------;;
;;  Arguments:                                                ;;
;;  n - index of item to remove (zero based)                  ;;
;;  l - list from which item is to be removed                 ;;
;;------------------------------------------------------------;;
;;  Returns:  List with item at index n removed               ;;
;;------------------------------------------------------------;;

(defun RemoveNth ( n l / i )
   (setq i -1)
   (vl-remove-if '(lambda ( x ) (= (setq i (1+ i)) n)) l)
)

Posted

I would look into the following code written by Lee Mac. This, I believe would solve the problem.

 

;;--------------------=={ Chain Selection }==-----------------;;
;;                                                            ;;
;;  Prompts the user to select an object and generates a      ;;
;;  selection chain of all objects sharing endpoints with     ;;
;;  objects in the accumulative selection.                    ;;
;;------------------------------------------------------------;;
;;  Author: Lee Mac, Copyright © 2012 - www.lee-mac.com       ;;
;;------------------------------------------------------------;;

(defun c:cs ( / en fl in l1 l2 s1 s2 sf vl )
   (setq sf
       (list
          '(-4 . "<OR")
              '(0 . "LINE,ARC")
              '(-4 . "<AND")
                  '(0 . "LWPOLYLINE,SPLINE")
                  '(-4 . "<NOT")
                      '(-4 . "&=")
                      '(70 . 1)
                  '(-4 . "NOT>")
              '(-4 . "AND>")
              '(-4 . "<AND")
                  '(0 . "POLYLINE")
                  '(-4 . "<NOT")
                      '(-4 . "&")
                      '(70 . 89)
                  '(-4 . "NOT>")
                  '(-4 . "AND>")
              '(-4 . "<AND")
                  '(0 . "ELLIPSE")
                  '(-4 . "<OR")
                      '(-4 . "<>")
                      '(41 . 0.0)
                      '(-4 . "<>")
                       (cons 42 (+ pi pi))
                  '(-4 . "OR>")
              '(-4 . "AND>")
          '(-4 . "OR>")
           (if (= 1 (getvar 'cvport))
               (cons 410 (getvar 'ctab))
              '(410 . "Model")
           )
       )
   )
   (if (setq s1 (ssget "_X" sf))
       (if (setq en (ssget "_+.:E:S" sf))
           (progn
               (setq s2 (ssadd)
                     en (ssname en 0)
                     l1 (list (vlax-curve-getstartpoint en) (vlax-curve-getendpoint en))
               )
               (repeat (setq in (sslength s1))
                   (setq en (ssname s1 (setq in (1- in)))
                         vl (cons (list (vlax-curve-getstartpoint en) (vlax-curve-getendpoint en) en) vl)
                   )
               )
               (while
                   (progn
                       (foreach v vl
                           (if (vl-some '(lambda ( p ) (or (equal (car v) p 1e- (equal (cadr v) p 1e-)) l1)
                               (setq s2 (ssadd (caddr v) s2)
                                     l1 (vl-list* (car v) (cadr v) l1)
                                     fl t
                               )
                               (setq l2 (cons v l2))
                           )
                       )
                       fl
                   )
                   (setq vl l2 l2 nil fl nil)
               )
           )
       )
       (princ "\nNo valid objects found.")
   )
   (sssetfirst nil s2)
   (princ)
)
(vl-load-com) (princ)

 

I haven't looked into this code too closely, but I would assume that the nodes/ lines would be entered into the selection set in order similar to the flow of a pipe.

 

regards,

 

Hippe013

Posted

MJML,

 

Then isn't it simply a matter of sorting your file on the first node ?

 

Before creating the file, do you keep it in a list ?

 

How is the diameter of the line kept ?

 

Like I told you, all the code I gave was so you could create

node with a numbering system not going beyond 99999

 

ymg

Posted

The following will number the vertices of your pipe network,

and then create an csv file of it.

 

If you open it in Excel, there will be a blank column for you to enter pipe diameters.

 

(defun c:test ()
 (setq fuzz 0.1) 
 (prompt "\nSelect Network : ")   
 (setq s (ssget '((0 . "LINE"))))
 ; Build Point list      
 (setq pl nil) 
 (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
           p1 (cdr (assoc 10 ent))
           pl (if (not (positionfuzz p1 pl)) (cons p1 pl) pl)
           p2 (cdr (assoc 11 ent))
           pl (if (not (positionfuzz p2 pl)) (cons p2 pl) pl)
    )
  )
  ; Draw Vertex Number   
  (foreach p pl
     (mk_mtext p (itoa (vl-position p pl)) 5 0.5 0)
  )

  ; Build the Edge List  
  (setq el nil)
  (repeat (setq i (sslength s))
    (setq ent (entget (ssname s (setq i (1- i))))
           p1 (cdr (assoc 10 ent))
           el (cons (positionfuzz p1 pl) el)
           p2 (cdr (assoc 11 ent))
           el (cons (positionfuzz p2 pl) el)            
     )
  )
  
  ;(setq pl (vl-sort pl (function (lambda (a b) (< (caddr a) (caddr b)))))	;; Sort on Z Coordinate     
  ;      pl (vl-sort pl (function (lambda (a b) (< (cadr  a) (cadr  b)))))	;; Sort on Y Coordinate     
  ;      pl (vl-sort pl (function (lambda (a b) (< (car   a) (car   b)))))      ;; Sort on X Coordinate    
                                                                                                         

  (setq xl nil)      
    (print el)
    (while el                                          
      (setq pol (list (cadr el)(car el))        ; We go reverse as we will cons the polyline             
            nxt (car pol)                       ; nxt will be our next edge                              
      el (cddr el)                       ; Remove first two edges from el                         
      )
      (if (not (positionfuzz nxt el))            ;The previous edge was an end of line                    
    (setq pol (reverse pol)              ;We reverse our Polyline                                 
	  nxt (car pol)                  ;and adjust our next edge                                
           )
      )
      
      (while (setq n (positionfuzz nxt el)) 	 
          (setq  el (removenth n el)
                  n (- n (rem n 2))
                pol (cons (nth n el) pol)
	  el (removenth n el)
                nxt (car pol) 
   )
   	 
   (if (not (positionfuzz nxt el))
      (setq pol (reverse pol)
            nxt (car pol)
      )
   )  
      )
      
      (setq xl (cons pol xl))
      
    )
    (setq xl (reverse xl))
          
    ; New Create the file
    (setq f (open "C:\\PIPENET.CSV" "a"))
    
    (write-line  "From, To,Diam.,Len/Dir"  f)
    (foreach pol xl
    (setq  fr (car pol)
           p1 (nth fr pl)
          pol (cdr pol)
    )       
    (repeat (length pol)
       (setq to (car pol)
             p2 (nth to pl)
              l (distance p1 p2)
              v (mapcar '- p2 p1)
              a (cond
                   ((> (car   v) fuzz)     "E")
                   ((< (car   v) (- fuzz)) "W")
                   ((> (cadr  v) fuzz)     "N")
                   ((< (cadr  v) (- fuzz)) "S")
                   ((> (caddr v) fuzz)     "U")
                   ((< (caddr v) (- fuzz)) "D")
                 )
       )
       (setq lin (strcat (itoa fr) "," (itoa to) ", ," a (rtos l 2 1)))
       (princ (strcat "\n" lin))      
       (write-line lin f)
       (setq fr to
             p1 p2
            pol (cdr pol)
       )
      ) 
    )
    (close f)      
    (princ)
)     




;;****************************************************************************;
;; mk_mtext                                                                   ;
;; Arguments: p,  Insertion Point.                                            ;
;;            s,  Text.                                                       ;
;;            j,  Justification:                                              ;
;;                1 = Top left; 2 = Top center; 3 = Top right;                ;
;;                4 = Middle left; 5 = Middle center; 6 = Middle right;       ;
;;                7 = Bottom left; 8 = Bottom center; 9 = Bottom right        ;
;;            h,  Text Height.                                                ;
;;            r,  Rotation.                                                   ;
;;****************************************************************************;

(defun mk_mtext (p s j h r)
  (entmakex
     (list (cons 0   "MTEXT")         
           (cons 100 "AcDbEntity")          
           (cons 100 "AcDbMText")    
           (cons 10 p)
           (cons 71 j)
           (cons 40 h)
           (cons 50 r)
           (cons  1 s)
     )
  )
)

;;----------------------=={ Remove Nth }==--------------------;;
;;                                                            ;;
;;  Removes the item at the nth index in a supplied list      ;;
;;------------------------------------------------------------;;
;;  Author: Lee Mac, Copyright © 2011 - www.lee-mac.com       ;;
;;------------------------------------------------------------;;
;;  Arguments:                                                ;;
;;  n - index of item to remove (zero based)                  ;;
;;  l - list from which item is to be removed                 ;;
;;------------------------------------------------------------;;
;;  Returns:  List with item at index n removed               ;;
;;------------------------------------------------------------;;

(defun RemoveNth ( n l / i )
   (setq i -1)
   (vl-remove-if '(lambda ( x ) (= (setq i (1+ i)) n)) l)
)

; positionfuzz   by irneb                                      ;

(defun positionfuzz (item sequence / p)
 (setq p -1)
 (if (vl-some
       (function (lambda (a)
                   (setq p (1+ p))
                   (equal item a fuzz)))
       sequence)
   p))

 

Ugly code, but seems to work!

 

ymg

pipenet.dwg

PIPENET.CSV

Pipenet.LSP

Posted

Hippe013,

 

For the selection set the CS routine most certainly work.

 

But because we would use the order in selection set s2

as the node number, some of the vertices of the line segments

would need to be reversed.

 

But this could be checked quite easily.

 

Would probably result in a cleaner routine than the one I submitted.

 

ymg

Posted (edited)
I would look into the following code written by Lee Mac. This, I believe would solve the problem.

 

I haven't looked into this code too closely, but I would assume that the nodes/ lines would be entered into the selection set in order similar to the flow of a pipe.

 

regards,

 

Hippe013

 

I m not sure what your code does. Is there a file generated or a list?

 

Edit: I think I got it. I m not sure how can I take advantage of it. I need to examine it closely.

Edited by MJLM
Posted
MJLM,

If you have antenna, that is not everything loops on itself, there will one list of integers per branch.

 

 

Well, unfortunately my pipe networks are often of the grid type, not of the tree type. This means like my picture above I have many closed loops.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...