Hi everyone
I want to make plugin for AutoCAD but i have some problems.
I want to make plugin that connect to server, send there current image and then server do something with it and send back results.
So I am actualy making 2 plugins. One on server side, other on client side.
Server side code:
Imports System.Net.Sockets Imports System.Text Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports System.Net Imports System.Threading Imports Autodesk.AutoCAD.EditorInput Namespace serverACDrendering Public Class Commands Dim close As Boolean = False Dim MAX_CLIENTS As Integer = 5 Dim queue(MAX_CLIENTS) As ClientWrapper Dim currentClient As Integer = 0 <CommandMethod("StartServer")> Public Sub StartServer() For i As Integer = 0 To MAX_CLIENTS queue(i) = Nothing Next Dim receiverThread As Thread = New Thread(AddressOf receiver) Dim transmitterThread As Thread = New Thread(AddressOf transmitter) receiverThread.Start() Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor ed.WriteMessage("\nreceiver thread started\n}") transmitterThread.Start() ed.WriteMessage("\ntransmitter thread Started\n") While (Not close) ed.WriteMessage("\nCreated both threads... Time to rock ...\n") End While End Sub
<CommandMethod("StopServer")> Public Sub StopServer() close = False End Sub End Class
So i have two more threads receiver thread and transmitter thread. First is getting clients and get file from them, second send results back to them.
The problem
While (Not close) ed.WriteMessage("\nCreated both threads... Time to rock ...\n") End While
In this while loop i shold take one client and do something with it.
When this loop starts i cant stop it from autocad with StopServer command (I think because autocad is busy).
I can't do this in seperate thread because autoCAD don't do multithreading.
What can i do to put this work in background ?
Regards Domen
You make AutoCAD to work as a server for both a receiver (get files from clients) and a transmitter (send results back to clients). When you start the server, both two methods receiver() and transmitter() send to execution at nearly the same time. Then the code goes to an infinite loop, write to the editor and keeps AutoCAD non-stopped busy. So there is no chance to enter the command StopServer on AutoCAD, as the editor is busy.
To stop the server, you may think to use the Timer (instead of while loop), set an interval time to check the clients. By that way, the method StartServer will have chance to avoid the infinite loop that does not allow this command to finish.
Because AutoCAD .NET API is single-threaded, make sure that there is a lock key to switch between receiver() and transmitter(). This static variable will allow only one method (receiver OR transmitter) to execute AutoCAD .NET functions at the time. We cannot let AutoCAD go multithreading.
-Khoa
Thanks for answer i repair it but ...
I put timer in code but i got another error: eNotApplicable
Public Class Commands Dim close As Boolean = False Dim MAX_CLIENTS As Integer = 5 Dim queue(MAX_CLIENTS) As ClientWrapper Dim currentClient As Integer = 0 Dim timer As New System.Timers.Timer Dim editor As Editor = Application.DocumentManager.MdiActiveDocument.Editor <CommandMethod("StartServer")> Public Sub StartServer() For i As Integer = 0 To MAX_CLIENTS queue(i) = Nothing Next Dim receiverThread As Thread = New Thread(AddressOf Receiver) Dim transmitterThread As Thread = New Thread(AddressOf Transmitter) receiverThread.Start() editor.WriteMessage(vbNewLine & " receiver thread started" & vbNewLine) transmitterThread.Start() editor.WriteMessage(vbNewLine & " transmitter thread Started" & vbNewLine) timer.Enabled = True timer.Interval = 15000 'every 15 seconds AddHandler timer.Elapsed, AddressOf Work End Sub Private Sub Work() editor.WriteMessage(vbNewLine & "working ..." & vbNewLine) End Sub
Error is rised in line "End Sub" from subroutine Work()
The Subroutines Receiver and Transmitter are empty functions for now
I would like that Receiver work non-stop so it can get clients all the time. Is there any way to do this?
I think the problem "eNotApplicable" came from the Timer running on another thread to trigger the time event. When it hit the time, this non-AutoCAD thread calls the AutoCAD editor and causes the thread violation with the current AutoCAD thread. We cannot have two threads refer to the same AutoCAD instance.
AutoCAD .NET (ObjectARX wrapper) is single-threaded (as it is not fully .NET managed code), so only one main thread allows to run per AutoCAD instance. I did a test without the Timer; it also caused an error of "CrossThreadMessagingException". So the problem is not from the Timer, it was from the new thread.
Using threads in AutoCAD .NET is very dangerous because it actually calls ObjectARX native code behind the scene. I would not use threads to call AutoCAD .NET functions. I don't know it may have another solution from official Autodesk developers.
You may try to remove threads from your code, and use TWO AutoCAD instances. One AutoCAD will work as a receiver; another AutoCAD will work as a transmitter. Then you open two AutoCAD and run different commands, one for receiver to get files from clients, another for transmitter to send results back to clients. Those two AutoCAD plugins refer to another DLL file just for data communication (non-AutoCAD code) between two AutoCAD instances. You may use While Loop again instead of Timer (as Timer may cause thread issue).
Hope it may help.
-Khoa
Thanks it helped me a lot.
So i decided to make plugin that will work for only one client at the time, when client connected i will render his file and send back photos.
I make some progress but got problem with socket. When i want read from socket on client side it say it's closed.
Sorry for having so much work with me
Server Side code:
Imports System.Net.Sockets Imports System.Text Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports System.Net Imports System.IO Imports Autodesk.AutoCAD.EditorInput Imports Autodesk.AutoCAD Namespace serverACDrendering Public Class Command Dim close As Boolean = False 'when true server ends Dim client As ClientWrapper 'queue that holds clients and their files Dim editor As Editor = Application.DocumentManager.MdiActiveDocument.Editor 'editor for writing to command line Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument Private Const portNum = 8000 Private Const ipServer = "127.0.0.1" Private PACKET_SIZE As UInt16 = 4096 Dim path As String = "c:\renderPluginACD\redering.dwg" <CommandMethod("StartServer")> Public Sub StartServer() editor.WriteMessage(vbNewLine & "starting ..." & vbNewLine) ' While (Not close) client = New ClientWrapper Receiver() Render() Transmitter() System.Threading.Thread.Sleep(2000) ' End While End Sub Private Sub Render() Dim Mstream As MemoryStream = client.GetFile If (Not Directory.Exists("c:\renderPluginACD")) Then Directory.CreateDirectory("c:\renderPluginACD") End If If (File.Exists(path)) Then File.Delete(path) End If Dim fs As FileStream = File.OpenWrite(path) Mstream.WriteTo(fs) client.SetResult(fs) fs.Close() End Sub 'receive connection and put file in queue Private Sub Receiver() Dim tcpListener As TcpListener Dim ip As IPAddress = IPAddress.Parse(ipServer) tcpListener = New TcpListener(ip, portNum) tcpListener.Start() Try Dim TcpClient = tcpListener.AcceptTcpClient() If (TcpClient.Connected) Then client.SetTcpClient(TcpClient) Dim Reader As BinaryReader Dim ReadBuffer(PACKET_SIZE) As Byte Dim NData As Int32 Dim MStream As MemoryStream Dim LData As Int32 Reader = New BinaryReader(TcpClient.GetStream) NData = Reader.ReadInt32 MStream = New MemoryStream While NData > 0 LData = TcpClient.GetStream.Read(ReadBuffer, 0, PACKET_SIZE) MStream.Write(ReadBuffer, 0, LData) NData -= LData End While editor.WriteMessage(vbNewLine & "received file ..." & vbNewLine) client.SetFile(MStream) End If Catch e As Exception End Try 'tcpListener.Stop() End Sub 'send back the resutls Private Sub Transmitter() Dim fs As FileStream = New FileStream(path, FileMode.Open, FileAccess.Read) Dim length As Integer = CType(fs.Length, Integer) Dim reader As New BinaryReader(fs) Dim buffer() As Byte ' Data buffer Dim Writer As New BinaryWriter(client.GetTcpClient.GetStream) Writer.Write(length) Do 'read data from file buffer = reader.ReadBytes(PACKET_SIZE) 'write data to Network Stream Writer.Write(buffer) Loop While buffer.Length = PACKET_SIZE Writer.Flush() Writer.Close() reader.Close() fs.Close() End Sub 'stops server <CommandMethod("StopServer")> Public Sub StopServer() close = False End Sub End Class 'wrapper for clients in queue Public Class ClientWrapper Dim tcpClient As TcpClient Dim file As Object Dim result As Object Dim flag As Integer Public Function GetTcpClient() As TcpClient Return tcpClient End Function Public Sub SetTcpClient(_tcpClient As TcpClient) tcpClient = _tcpClient End Sub Public Function GetFile() As Object Return file End Function Public Sub SetFile(_file As Object) file = _file End Sub Public Function GetResult() As Object Return result End Function Public Sub SetResult(_result As Object) result = _result End Sub End Class End Namespace
Client side Code:
Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports System.Net.Sockets Imports System.Text Imports System Imports System.IO Imports Autodesk.AutoCAD.DatabaseServices Imports app = Autodesk.AutoCAD.ApplicationServices.Application Namespace Plugin1 Public Class Commands Private Const portNum = 8000 Private Const ipServer = "127.0.0.1" Private Const BUFFER_SIZE As Integer = 8192 Dim isActive As Boolean = False Dim acDoc As Document Dim receive As Object Private PACKET_SIZE As UInt16 = 4096 Dim tcpClient As New System.Net.Sockets.TcpClient() <CommandMethod("RenderOnHPC")> Public Sub SendToRenderer() Save() Connect() Send() ReceivePhoto() End Sub Private Sub Save() acDoc = Application.DocumentManager.MdiActiveDocument Dim strDWGName As String = acDoc.Name Dim obj As Object = Application.GetSystemVariable("DWGTITLED") strDWGName = "c:\renderPluginACD\MyDrawing.dwg" 'Save the active drawing If (Not Directory.Exists("c:\renderPluginACD")) Then Directory.CreateDirectory("c:\renderPluginACD") End If acDoc.Database.SaveAs(strDWGName, False, DwgVersion.Current, acDoc.Database.SecurityParameters) acDoc.Database.Dispose() End Sub Private Sub Connect() tcpClient.Connect(ipServer, portNum) End Sub Private Sub Send() Dim fs As FileStream = New FileStream("c:\renderPluginACD\MyDrawing.dwg", FileMode.Open, FileAccess.Read) Dim length As Integer = CType(fs.Length, Integer) Dim reader As New BinaryReader(fs) Dim buffer() As Byte ' Data buffer Try tcpClient.GetStream() Dim Writer As New BinaryWriter(tcpClient.GetStream()) Writer.Write(length) Do 'read data from file buffer = reader.ReadBytes(PACKET_SIZE) 'write data to Network Stream Writer.Write(buffer) Loop While buffer.Length = PACKET_SIZE Writer.Flush() Writer.Close() reader.Close() Catch ex As System.Exception ' Handle errors End Try fs.Close() End Sub Private Sub ReceivePhoto() Dim renderedPath As String = "c:\renderPluginACD\return.dwg" Dim reader2 As BinaryReader Dim readBuffer(PACKET_SIZE) As Byte Dim nData As Int32 Dim mStream As MemoryStream Dim lData As Int32 reader2 = New BinaryReader(tcpClient.GetStream()) nData = reader2.ReadInt32 MStream = New MemoryStream While NData > 0 LData = tcpClient.GetStream.Read(ReadBuffer, 0, PACKET_SIZE) MStream.Write(ReadBuffer, 0, LData) NData -= LData End While If (File.Exists(RenderedPath)) Then File.Delete(RenderedPath) End If Dim fs As FileStream = File.OpenWrite(RenderedPath) MStream.WriteTo(fs) End Sub End Class End Namespace
Hi,
I ran your code and saw the error came from the disconnected TcpClient. On the client Send() function, when you close the BinaryWriter, it will also dispose the NetworkStream and close the reference TcpClient connection. So just comment out writer.Close() and it's good to go.
We need to open two AutoCAD instances, one types the command "StartServer", another types the command "RenderOnHPC" and we will see how the client saves DWG files.
It is interesting to make two AutoCAD working as a server and a client, and then they communicate each other by network streams using TCP (http://127.0.0.1:8000). With the TCP listening event, it does not need to use While Loop. I am guessing it still has a lot of work with this idea.
I copied your code, did some minor changes and had the final working code:
Public Class ServerPlugin Private Const PortNum As Integer = 8000 Private Const IpServer As String = "127.0.0.1" Private Const PACKET_SIZE As UInt16 = 4096 'when true server ends Private close As Boolean = False 'queue that holds clients and their files Private client As ClientWrapper 'editor for writing to command line Private ReadOnly editor As Editor = Application.DocumentManager.MdiActiveDocument.Editor Private acDoc As Document = Application.DocumentManager.MdiActiveDocument Private path As String = "c:\renderPluginACD\rendering.dwg" Private tcpClient As System.Net.Sockets.TcpClient <CommandMethod("StartServer")> _ Public Sub StartServer() editor.WriteMessage(vbLf + "starting ..." + vbLf) ' While (Not close) client = New ClientWrapper() Receiver() Render() Transmitter() Disconnect() System.Threading.Thread.Sleep(2000) ' End While End Sub 'receive connection and put file in queue Private Sub Receiver() Dim ip As IPAddress = IPAddress.Parse(IpServer) Dim tcpListener = New TcpListener(ip, PortNum) tcpListener.Start() Try ' perform a blocking call to accept requests tcpClient = tcpListener.AcceptTcpClient() ' keep waiting until a client is connected at http://127.0.0.1:8000 If tcpClient.Connected Then client.SetTcpClient(tcpClient) Dim readBuffer = New Byte(PACKET_SIZE) {} Dim reader = New BinaryReader(tcpClient.GetStream()) Dim nData As Integer = reader.ReadInt32() Dim mStream = New MemoryStream() While nData > 0 Dim lData As Integer = tcpClient.GetStream().Read(readBuffer, 0, PACKET_SIZE) mStream.Write(readBuffer, 0, lData) nData -= lData End While editor.WriteMessage(vbLf + "received file ..." + vbLf) client.SetFile(mStream) End If Catch e As System.Exception End Try 'tcpListener.Stop() End Sub Private Sub Render() Dim mStream = DirectCast(client.GetFile(), MemoryStream) If Not Directory.Exists("c:\renderPluginACD") Then Directory.CreateDirectory("c:\renderPluginACD") End If If File.Exists(path) Then File.Delete(path) End If Dim fs As FileStream = File.OpenWrite(path) mStream.WriteTo(fs) client.SetResult(fs) fs.Close() End Sub 'send back the resutls Private Sub Transmitter() Dim fs = New FileStream(path, FileMode.Open, FileAccess.Read) Dim length As Integer = Convert.ToInt32(fs.Length) Dim reader = New BinaryReader(fs) Dim buffer As Byte() ' Data buffer Dim writer = New BinaryWriter(client.GetTcpClient().GetStream()) writer.Write(length) Do 'read data from file buffer = reader.ReadBytes(PACKET_SIZE) 'write data to Network Stream writer.Write(buffer) Loop While buffer.Length = PACKET_SIZE writer.Flush() writer.Close() reader.Close() fs.Close() End Sub Private Sub Disconnect() ' shutdown and end connection tcpClient.Close() End Sub 'stops server <CommandMethod("StopServer")> _ Public Sub StopServer() close = False End Sub End Class 'wrapper for clients in queue Public Class ClientWrapper Private tcpClient As TcpClient Private file As Object Private result As Object Private flag As Integer Public Function GetTcpClient() As TcpClient Return tcpClient End Function Public Sub SetTcpClient(_tcpClient As TcpClient) tcpClient = _tcpClient End Sub Public Function GetFile() As Object Return file End Function Public Sub SetFile(_file As Object) file = _file End Sub Public Function GetResult() As Object Return result End Function Public Sub SetResult(_result As Object) result = _result End Sub End Class Public Class ClientPlugin Private Const PortNum As Integer = 8000 Private Const IpServer As String = "127.0.0.1" Private Const BUFFER_SIZE As Integer = 8192 Private Const PACKET_SIZE As UInt16 = 4096 Private isActive As Boolean = False Private receive As Object Private tcpClient As System.Net.Sockets.TcpClient <CommandMethod("RenderOnHPC")> _ Public Sub SendToRenderer() Save() Connect() Send() ReceivePhoto() Disconnect() End Sub Private Sub Save() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using db Dim strDWGName As String = doc.Name Dim obj As Object = Application.GetSystemVariable("DWGTITLED") strDWGName = "c:\renderPluginACD\MyDrawing.dwg" 'Save the active drawing If Not Directory.Exists("c:\renderPluginACD") Then Directory.CreateDirectory("c:\renderPluginACD") End If db.SaveAs(strDWGName, False, DwgVersion.Current, doc.Database.SecurityParameters) End Using End Sub Private Sub Connect() tcpClient = New System.Net.Sockets.TcpClient() tcpClient.Connect(IpServer, PortNum) End Sub Private Sub Disconnect() ' shutdown and end connection tcpClient.Close() End Sub Private Sub Send() Dim fs = New FileStream("c:\renderPluginACD\MyDrawing.dwg", FileMode.Open, FileAccess.Read) Dim length As Integer = Convert.ToInt32(fs.Length) Dim reader = New BinaryReader(fs) ' Data buffer Try Dim stream As NetworkStream = tcpClient.GetStream() Dim writer = New BinaryWriter(stream) writer.Write(length) Dim buffer As Byte() Do 'read data from file buffer = reader.ReadBytes(PACKET_SIZE) 'write data to Network Stream writer.Write(buffer) Loop While buffer.Length = PACKET_SIZE writer.Flush() 'writer.Close(); // do not close the TcpClient connection reader.Close() ' Handle errors Catch ex As System.Exception End Try fs.Close() End Sub Private Sub ReceivePhoto() Dim renderedPath As String = "c:\renderPluginACD\return.dwg" Dim readBuffer = New Byte(PACKET_SIZE) {} Dim reader2 = New BinaryReader(Me.tcpClient.GetStream()) Dim nData As Integer = reader2.ReadInt32() Using mStream = New MemoryStream() While nData > 0 Dim lData As Integer = Me.tcpClient.GetStream().Read(readBuffer, 0, PACKET_SIZE) mStream.Write(readBuffer, 0, lData) nData -= lData End While If File.Exists(renderedPath) Then File.Delete(renderedPath) End If Dim fs As FileStream = File.OpenWrite(renderedPath) mStream.WriteTo(fs) fs.Close() End Using End Sub End Class
-Khoa
Thanks it works great.
Now i want to upgrade code that it will actualy render file and instead returning dwg send back picture.
I was looking throu internet for some exsamples and found only one useful link. So i try use it and do following:
Private Sub Render() Dim mStream = DirectCast(client.GetFile(), MemoryStream) If Not Directory.Exists("c:\renderPluginACD") Then Directory.CreateDirectory("c:\renderPluginACD") End If If File.Exists(path) Then File.Delete(path) End If Dim fs As FileStream = File.OpenWrite(path) mStream.WriteTo(fs) fs.Close()
'i saved file to here
' now i want to open it and render it Dim doc As Document = Application.DocumentManager.MdiActiveDocument doc.Database.ReadDwgFile(path, FileOpenMode.OpenTryForReadShare, True, Nothing) Dim fileName As String = "c:\renderPluginACD\picture.png" Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As View = doc.GraphicsManager.GetGsView(vpn, True) Dim view As View = gsv.Clone(True, True) Dim dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() 'dev.OnSize(doc.GraphicsManager.DeviceIndependentDisplaySize) dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Dim bitmap As Bitmap = view.RenderToImage bitmap.Save(fileName) fs = File.OpenRead(fileName) client.SetResult(fs) End Sub
The problems occurs with reading dwg file.
I am a bit late to answer as I am working at day time (US CST).
There are some points to solve the problem:
1. You should not use ReadDwgFile() from the current database of MdiActiveDocument as it will cause "eRepeatedDwgRead" error. Reading an external DWG file from the current DWG file is not allowed. It's better to use database.Insert method. Instead, we will use Application.DocumentManager.Open(dwgPath).
2. There is nothing to render on the DWG source file (c:\renderPluginACD\rendering.dwg). This file should not be empty or have no Modelling entities. The DWG rendering file should have some Modelling objects (Sphere, Box, Cylinder...) to render. Otherwise, it will cause error at view.RenderToImage() method. I already added the code to check this exception error (view.BackgroundId == ObjectId.Null).
3. If I put some Modelling entities on rendering.dwg, the code will output an image (picture.png) correctly. So you have to work to pass entities to render. There is nothing to render now so you won't see anything.
4. You should use Using to wrap the code to release unmanaged objects after finishing the method. I put the code with Using from Kean's code back.
Please see my fixed code:
Private Sub Render() Dim mStream = DirectCast(client.GetFile(), MemoryStream) If Not Directory.Exists("c:\renderPluginACD") Then Directory.CreateDirectory("c:\renderPluginACD") End If If File.Exists(path) Then File.Delete(path) End If Dim fs As FileStream = File.OpenWrite(path) mStream.WriteTo(fs) fs.Close() 'i saved file to here ' now i want to open it and render it Dim doc As Document = Application.DocumentManager.Open(path) Dim fileName As String = "c:\renderPluginACD\picture.png" Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As GraphicsSystem.View = doc.GraphicsManager.GetGsView(vpn, True) Using view As GraphicsSystem.View = gsv.Clone(True, True) ' Do not render the drawing if there are no rendering objects If view.BackgroundId = ObjectId.Null Then Return End If Using dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() 'dev.OnSize(doc.GraphicsManager.DeviceIndependentDisplaySize) dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Using bitmap As Bitmap = view.RenderToImage() bitmap.Save(fileName) fs = File.OpenRead(fileName) client.SetResult(fs) End Using End Using End Using End Sub
-Khoa
Hi
the solution you wrote don't work for me because i am working on AutoCAD 2013 and therefor ObjectARX for AutoCAD 2013.
I repaired in this way:
Private Sub Render() Dim mStream = DirectCast(client.GetFile(), MemoryStream) If Not Directory.Exists("c:\renderPluginACD") Then Directory.CreateDirectory("c:\renderPluginACD") End If If File.Exists(path) Then File.Delete(path) End If Dim fs As FileStream = File.OpenWrite(path) mStream.WriteTo(fs) fs.Close() Dim docCol As DocumentCollection = Application.DocumentManager Dim doc As Document doc = DocumentCollectionExtension.Open(docCol, path) Dim fileName As String = "c:\renderPluginACD\picture.png" Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As GraphicsSystem.View = doc.GraphicsManager.GetGsView(vpn, True) Using view As GraphicsSystem.View = gsv.Clone(True, True) ' Do not render the drawing if there are no rendering objects If view.BackgroundId = ObjectId.Null Then Return End If Using dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Using bitmap As Bitmap = view.RenderToImage() bitmap.Save(fileName) fs = File.OpenRead(fileName) client.SetResult(fs) End Using End Using End Using End Sub
Problem happens when it's called renderToImage() function. I think that's because on server they are actualy opened 2 files. First is empty drawing that is there from the start and another drawing that i send from the client(it openes in new window)
I use AutoCAD 2009 at work so I did not test it with 2013. The code was from C# that converted to VB.
To bypass the error of RenderToImage() function, you need to put some modelling entities in the file rendering.dwg to render. I have to use breakpoints on VS debugging to stop the code execution, open file rendering.dwg in AutoCAD, draw some 3D objects, save and close it. Then release the breakpoint to allow .NET code to continue. It works for me to produce an image (picture.png).
I will review the whole code by the end of the day. The code is not completed so I have to debug and insert something during the whole process to make it works.
-Khoa
I rewrote your code to make it more logical and understandable to read. There is only one class ClientServerPlugin for both client and server. The problem is that the code is not completed for the client side to send DWG file to the server. So there is nothing to render on the server then it causes error on RenderToImage() method. Before going further to fix, there are some questions to clarify:
1. What is the purpose for this server-client project? Do we need one server and many clients connected? By that way, the server will work asynchronously to listen for requests from many clients. We assume one client is one AutoCAD instance in one user machine.
2. Each client sends its drawing with 3D objects to render. The server will take it and render to an image (PNG format), save it to a network drive, and return WHAT to the client? It is quite confusing that the client then generates a result DWG file but what's inside this file?
For testing, we need to open two AutoCAD, one (server side) types "StartServer" command, then another (client side) types "RenderOnServer" command. But the image is not saved yet. It will get fixed later.
I get to sleep now. The following is my new refactoring code:
''' <summary> ''' Wrapper for clients in queue ''' </summary> Public Structure ClientWrapper Public Property TcpClient() As TcpClient Get Return m_TcpClient End Get Set m_TcpClient = Value End Set End Property Private m_TcpClient As TcpClient Public Property File() As MemoryStream Get Return m_File End Get Set m_File = Value End Set End Property Private m_File As MemoryStream Public Property Result() As FileStream Get Return m_Result End Get Set m_Result = Value End Set End Property Private m_Result As FileStream End Structure ''' <summary> ''' Client server plugin ''' </summary> Public Class ClientServerPlugin Private Const IpServer As String = "127.0.0.1" Private Const PortNum As Integer = 8000 Private Const PacketSize As Integer = 4096 Private Const SavedDirectory As String = "C:\\RenderPluginACD\\" Private _client As ClientWrapper ' Queue that holds clients and their files Private ReadOnly _editor As Editor = Application.DocumentManager.MdiActiveDocument.Editor Private _tcpClient As System.Net.Sockets.TcpClient #Region "ClientPlugin" <CommandMethod("RenderOnServer")> _ Public Sub SendToRenderer() Connect() Dim inputFileName As String = "MyDrawing.dwg" Dim outputFileName As String = "Return.dwg" Save(inputFileName) Send(inputFileName) ReceivePhoto(outputFileName) Disconnect() End Sub Private Sub Connect() _tcpClient = New System.Net.Sockets.TcpClient() _tcpClient.Connect(IpServer, PortNum) End Sub Private Sub Disconnect() ' Shutdown and end connection _tcpClient.Close() End Sub ''' <summary> ''' Save the current drawing to a file ''' </summary> ''' <param name="fileName">file name to save</param> Private Sub Save(fileName As String) Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using db 'object obj = Application.GetSystemVariable("DWGTITLED"); ' Save the active drawing If Not Directory.Exists(SavedDirectory) Then Directory.CreateDirectory(SavedDirectory) End If db.SaveAs(SavedDirectory + fileName, False, DwgVersion.Current, doc.Database.SecurityParameters) End Using End Sub ''' <summary> ''' Send a drawing file to a network stream ''' </summary> ''' <param name="fileName">file name to open and send to TCP</param> Private Sub Send(fileName As String) Dim fs = New FileStream(SavedDirectory + fileName, FileMode.Open, FileAccess.Read) Dim length As Integer = Convert.ToInt32(fs.Length) Dim reader = New BinaryReader(fs) Try Dim stream As NetworkStream = _tcpClient.GetStream() Dim writer = New BinaryWriter(stream) writer.Write(length) Dim buffer As Byte() Do ' Read data from file buffer = reader.ReadBytes(PacketSize) ' Write data to Network Stream writer.Write(buffer) Loop While buffer.Length = PacketSize writer.Flush() 'writer.Close(); // do not close the TcpClient connection reader.Close() ' Handle errors Catch ex As System.Exception End Try fs.Close() End Sub ''' <summary> ''' Receive the rendered photo file back ''' </summary> ''' <param name="fileName">photo file name</param> Private Sub ReceivePhoto(fileName As String) Dim filePath As String = SavedDirectory + fileName Dim readBuffer = New Byte(PacketSize) {} Dim receivedStream As NetworkStream = _tcpClient.GetStream() Dim reader = New BinaryReader(receivedStream) Dim streamLength As Integer = reader.ReadInt32() Using mStream = New MemoryStream() While streamLength > 0 Dim lData As Integer = receivedStream.Read(readBuffer, 0, PacketSize) mStream.Write(readBuffer, 0, lData) streamLength -= lData End While If File.Exists(filePath) Then File.Delete(filePath) End If Dim fs As FileStream = File.OpenWrite(filePath) mStream.WriteTo(fs) fs.Close() End Using End Sub #End Region #Region "ServerPlugin" <CommandMethod("StartServer")> _ Public Sub StartServer() Dim dwgFileName As String = "Rendering.dwg" Dim imageFileName As String = "Picture.png" _editor.WriteMessage(vbLf + "Starting ..." + vbLf) Receiver() Render(dwgFileName, imageFileName) Transmitter(dwgFileName) Disconnect() System.Threading.Thread.Sleep(2000) End Sub ''' <summary> ''' Receive connection and put file in queue ''' </summary> Private Sub Receiver() Dim ipAddress__1 As IPAddress = IPAddress.Parse(IpServer) Dim tcpListener = New TcpListener(ipAddress__1, PortNum) tcpListener.Start() Try ' Perform a blocking call to accept requests _tcpClient = tcpListener.AcceptTcpClient() ' Keep waiting until a client is connected at http://127.0.0.1:8000 If _tcpClient.Connected Then _client = New ClientWrapper() _client.TcpClient = _tcpClient Dim readBuffer = New Byte(PacketSize) {} Dim requestStream As NetworkStream = _tcpClient.GetStream() Dim reader = New BinaryReader(requestStream) Dim streamLength As Integer = reader.ReadInt32() ' convert a network stream to a memory stream Dim responseStream = New MemoryStream() While streamLength > 0 Dim lData As Integer = requestStream.Read(readBuffer, 0, PacketSize) responseStream.Write(readBuffer, 0, lData) streamLength -= lData End While _editor.WriteMessage(vbLf + "Received file ..." + vbLf) _client.File = responseStream End If Catch e As System.Exception End Try tcpListener.[Stop]() End Sub ''' <summary> ''' Render a DWG drawing to a PNG image ''' </summary> ''' <param name="dwgFileName">DWG file name to render</param> ''' <param name="imageFileName">PNG file name of the output</param> Private Sub Render(dwgFileName As String, imageFileName As String) Dim filePath As String = SavedDirectory + dwgFileName Dim mStream As MemoryStream = _client.File If Not Directory.Exists(SavedDirectory) Then Directory.CreateDirectory(SavedDirectory) End If If File.Exists(filePath) Then File.Delete(filePath) End If Dim fs As FileStream = File.OpenWrite(filePath) mStream.WriteTo(fs) fs.Close() ' Open a drawing in AutoCAD 2009 Dim doc As Document = Application.DocumentManager.Open(filePath) ' Open a drawing in AutoCAD 2013 'Dim docCol As DocumentCollection = Application.DocumentManager 'Dim doc As Document 'doc = DocumentCollectionExtension.Open(docCol, filePath) filePath = SavedDirectory + imageFileName Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As GraphicsSystem.View = doc.GraphicsManager.GetGsView(vpn, True) Using view As GraphicsSystem.View = gsv.Clone(True, True) ' Do not render the drawing if there are no rendering objects 'if (view.BackgroundId == ObjectId.Null) return; Using dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() 'dev.OnSize(doc.GraphicsManager.DeviceIndependentDisplaySize) dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Try Dim bitmap As Bitmap = view.RenderToImage() Using bitmap bitmap.Save(filePath) fs = File.OpenRead(filePath) _client.Result = fs End Using Catch ex As System.Exception End Try End Using End Using End Sub ''' <summary> ''' Send back the result ''' </summary> ''' <param name="fileName"></param> Private Sub Transmitter(fileName As String) Dim fs = New FileStream(SavedDirectory + fileName, FileMode.Open, FileAccess.Read) Dim length As Integer = Convert.ToInt32(fs.Length) Dim reader = New BinaryReader(fs) Dim buffer As Byte() ' Data buffer Dim writer = New BinaryWriter(_client.TcpClient.GetStream()) writer.Write(length) Do 'read data from file buffer = reader.ReadBytes(PacketSize) 'write data to Network Stream writer.Write(buffer) Loop While buffer.Length = PacketSize writer.Flush() writer.Close() reader.Close() fs.Close() End Sub 'stops server <CommandMethod("StopServer")> _ Public Sub StopServer() End Sub #End Region End Class
-Khoa
Hi Khoa
Let's say i am an arhitect. i made drawing of some building and i want some photos of my new building. I set up everything to render, but instead to push the button render i use my "SENDTORENDER" command. Rendering can be sometimes operation that takes a really long time. So when i will use command i will send this current drawing to some more powerful PC and so it will take less time to render file. After rendering on powerful PC ends it' will send image back to my PC.
So in short we have one powerfull PC in office that will render files and all the employees will send to this computer work. Since we can't make it multithreading i can't have one thread taking clients all the time. So server will work in while(true) loop and thaking clients. If it will be busy nobody can't connect.
Sorry for confusion other thay with sending back dwg file i just want to test if tcp conversation works both ways fine.
Thank for so much help
You are welcome. This is a very interesting topic to deal with multi-threading and parallel programming in AutoCAD. Gopinath Target from Autodesk had a lesson about it on AU, but it was dealing with how to invoke the main AutoCAD UI thread in the background process. He also uses a BackgroundWorker to perform asynchronous operations in a background thread. Let me time to play with his solution.
The idea to use TCP as an asynchronous communication between server and clients is very similar to the popular Node.js (Server-side JavaScript based on Google's V8 engine). This Google engine is also internally single-threaded like AutoCAD or RealDWG. The TCP solution will become the cloud computing one as seen on the ADN DevBlog ( http://adndevblog.typepad.com/cloud_and_mobile/ ). Autodesk already has Autodesk 360 Rendering ( http://rendering.360.autodesk.com/ ) service to remotely render images on the cloud. However, we want to learn the technology secrets behind. It's tough to catch all those new good things as we have to spend time on company projects every day.
To keep it simple with your project, I will go back to use TCP concept for non-blocking requests to render images on the server.
-Khoa
Hi dj-nemo,
The problem of view.RenderToImage() is the missing of HostApplicationServices.WorkingDatabase = doc.Database. Now it works to produce a PNG image.
We use TcpClient to connect, send, and receive stream data over a network in synchronous blocking mode. Each server and client has its own TcpClient to request and response network stream back to the central TCP/IP. This HTTP address is the gateway for communication between server and client.
The order of communication between server and client shows as below:
1. Server.ListenAndReceive
2. Client.SendStreamToServer
3. Server.Render
4. Server.ResponeStreamToClient
5. Client.ReceiveStreamFromServer
Open two AutoCAD, one works as a server with command StartRenderServer first, and another works as a client with command SendToRender. After running the server AutoCAD, on the client AutoCAD, draw some 3D objects, then hit command SendToServer. The client will save a new rendered image (Picture.png) to folder C:\ClientServer.
I rewrote the new code with better reusable methods. But you still have to test and add more features in your project. This code is just basic without multiple connected clients and something more, and has some possible potential bugs. I keep it simple and fundamental to easily understand.
#Region "Client-Server" ''' <summary> ''' Wrapper for clients in queue ''' </summary> Public Structure ClientWrapper Public Property File() As MemoryStream Get Return m_File End Get Set m_File = Value End Set End Property Private m_File As MemoryStream Public Property Result() As FileStream Get Return m_Result End Get Set m_Result = Value End Set End Property Private m_Result As FileStream End Structure ''' <summary> ''' Server-Client plugin ''' </summary> Public Class ServerClientPlugin Private Const IpServer As String = "127.0.0.1" Private Const PortNum As Integer = 8000 Private Const PacketSize As Integer = 4096 Private Const ServerFolder As String = "C:\ServerFolder\" ' A shared folder in a network drive Private Const ClientFolder As String = "C:\ClientFolder\" ' A local folder at client machine Private _client As ClientWrapper Private ReadOnly _editor As Editor = Application.DocumentManager.MdiActiveDocument.Editor Private _tcpClient As System.Net.Sockets.TcpClient #Region "ServerPlugin" <CommandMethod("StartRenderServer")> _ Public Sub StartRenderServer() Dim dwgFileName As String = "ServerDrawing.dwg" Dim imageFileName As String = "Picture.png" _editor.WriteMessage(vbLf + "Starting ..." + vbLf) ListenAndReceive() Render(dwgFileName, imageFileName) ResponseStreamToClient(imageFileName) DisconnectTCP() 'System.Threading.Thread.Sleep(2000); End Sub ''' <summary> ''' Receive connection and put file in queue ''' </summary> Private Sub ListenAndReceive() Dim ipAddress__1 As IPAddress = IPAddress.Parse(IpServer) Dim tcpListener = New TcpListener(ipAddress__1, PortNum) tcpListener.Start() Try ' Perform a blocking call to accept requests _tcpClient = tcpListener.AcceptTcpClient() ' Keep waiting until a client is connected at http://127.0.0.1:8000 If _tcpClient.Connected Then 'TcpClient = _tcpClient, _client = New ClientWrapper() With { _ .File = ReadStreamFromTCP() _ } _editor.WriteMessage(vbLf + "Received file ..." + vbLf) End If Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try tcpListener.[Stop]() End Sub ''' <summary> ''' Render a DWG drawing to a PNG image ''' </summary> ''' <param name="dwgFileName">DWG file name to render</param> ''' <param name="imageFileName">PNG file name of the output</param> Private Sub Render(dwgFileName As String, imageFileName As String) Try Dim filePath As String = ServerFolder + dwgFileName Dim responseStream As MemoryStream = _client.File If Not Directory.Exists(ServerFolder) Then Directory.CreateDirectory(ServerFolder) End If If File.Exists(filePath) Then File.Delete(filePath) End If Dim fileStream As FileStream = File.OpenWrite(filePath) responseStream.WriteTo(fileStream) fileStream.Close() ' Open the new saved drawing in AutoCAD 2009 Dim doc As Document = Application.DocumentManager.Open(filePath) ' Open the new saved drawing in AutoCAD 2013 'Dim docCol As DocumentCollection = Application.DocumentManager 'Dim doc As Document 'doc = DocumentCollectionExtension.Open(docCol, filePath) Using doc Dim workDb As Database = HostApplicationServices.WorkingDatabase HostApplicationServices.WorkingDatabase = doc.Database filePath = ServerFolder + imageFileName Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As GraphicsSystem.View = doc.GraphicsManager.GetGsView(vpn, True) Using view As GraphicsSystem.View = gsv.Clone(True, True) Using dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() 'dev.OnSize(doc.GraphicsManager.DeviceIndependentDisplaySize) dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Dim bitmap As Bitmap = view.RenderToImage() Using bitmap bitmap.Save(filePath) fileStream = File.OpenRead(filePath) _client.Result = fileStream End Using End Using End Using ' Restore the previous working database back HostApplicationServices.WorkingDatabase = workDb End Using Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try End Sub ''' <summary> ''' Send back the result ''' </summary> ''' <param name="fileName"></param> Private Sub ResponseStreamToClient(fileName As String) Try Dim fileStream = New FileStream(ServerFolder + fileName, FileMode.Open, FileAccess.Read) WriteStreamToTCP(fileStream) fileStream.Close() Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try End Sub 'stops server <CommandMethod("StopRenderServer")> _ Public Sub StopRenderServer() End Sub #End Region #Region "ClientPlugin" <CommandMethod("SendToRender")> _ Public Sub SendToRender() Dim inputFileName As String = "ClientDrawing.dwg" Dim outputFileName As String = "Picture.png" ConnectTCP() SaveCurrentDrawingToFile(inputFileName) SendStreamToServer(inputFileName) ReceiveStreamFromServer(outputFileName) DisconnectTCP() End Sub Private Sub ConnectTCP() Try _tcpClient = New System.Net.Sockets.TcpClient() _tcpClient.Connect(IpServer, PortNum) Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try End Sub Private Sub DisconnectTCP() ' Shutdown and end connection _tcpClient.Close() End Sub ''' <summary> ''' Save the current drawing to a file ''' </summary> ''' <param name="fileName">file name to save</param> Private Sub SaveCurrentDrawingToFile(fileName As String) Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using db 'object obj = Application.GetSystemVariable("DWGTITLED"); ' Save the active drawing If Not Directory.Exists(ClientFolder) Then Directory.CreateDirectory(ClientFolder) End If db.SaveAs(ClientFolder + fileName, False, DwgVersion.Current, doc.Database.SecurityParameters) End Using End Sub ''' <summary> ''' Send a drawing file to a network stream ''' </summary> ''' <param name="fileName">file name to open and send to TCP</param> Private Sub SendStreamToServer(fileName As String) Try Dim fileStream = New FileStream(ClientFolder + fileName, FileMode.Open, FileAccess.Read) WriteStreamToTCP(fileStream) fileStream.Close() Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try End Sub ''' <summary> ''' Receive the rendered photo file back ''' </summary> ''' <param name="fileName">photo file name</param> Private Sub ReceiveStreamFromServer(fileName As String) Try Dim responseStream = ReadStreamFromTCP() Dim filePath As String = ClientFolder + fileName If File.Exists(filePath) Then File.Delete(filePath) End If Dim fileStream As FileStream = File.OpenWrite(filePath) ' Save memory stream to physical file responseStream.WriteTo(fileStream) fileStream.Close() Catch ex As System.Exception _editor.WriteMessage(ex.Message + vbLf + ex.StackTrace) End Try End Sub #End Region #Region "Shared methods" Private Function ReadStreamFromTCP() As MemoryStream Dim readBuffer = New Byte(PacketSize) {} ' Get a network stream from TCP/IP address Dim requestStream As NetworkStream = _tcpClient.GetStream() ' Read binary stream from TCP/IP address Dim reader = New BinaryReader(requestStream) Dim streamLength As Integer = reader.ReadInt32() ' Convert a network stream to a memory stream Dim responseStream = New MemoryStream() While streamLength > 0 Dim lData As Integer = requestStream.Read(readBuffer, 0, PacketSize) responseStream.Write(readBuffer, 0, lData) streamLength -= lData End While Return responseStream End Function Private Sub WriteStreamToTCP(stream As Stream) Dim reader = New BinaryReader(stream) ' Get a network stream from TCP/IP address Dim requestStream As NetworkStream = _tcpClient.GetStream() ' Write binary stream to TCP/IP address Dim writer = New BinaryWriter(requestStream) Dim length As Integer = Convert.ToInt32(stream.Length) writer.Write(length) Dim buffer As Byte() Do ' Read data from file buffer = reader.ReadBytes(PacketSize) ' Write data to Network Stream writer.Write(buffer) Loop While buffer.Length = PacketSize writer.Flush() 'writer.Close(); // do not close the TcpClient connection reader.Close() End Sub #End Region End Class
-Khoa
I found one more problem in line with type Bitmap
Dim bitmap As Bitmap = view.RenderToImage()
The problem is i can't import System.Drawing
It's says
Namespace or type specified in the Imports 'System.Drawing' doesn't contain any public member or cannot be found. Make sure the namespace or the type is defined and contains at least one public member. Make sure the imported element name doesn't use any aliases.
Should i use something instead or can i inport somehow dll that i had downloaded from internet?
Hi dj-nemo,
I hope you may solve this .NET reference issue. System.Drawing namespace is seen from all .NET frameworks since 1.1, 2.0, 3.0, 3.5, 4.0 and 4.5. See the MSDN link: http://msdn.microsoft.com/en-us/library/xs6ftd89(v=vs.100)
Please check the "Add Reference" dialog in Visual Studio to import the correct System.Drawing version in your project. It will also work if it has lower .NET version with your current AutoCAD plug-in.
-Khoa
Hi
after adding System.drawing via add reference it is ok.
Now i have picture rendered from the top in 2D mode. How can i put view in same position cient has.
I am attaching example of my simple dwg file, wanted bitmap and bitmap i get
Hi
i found another question. I was wondering if there is any document from autodesk to tell me how it's rendering made. As i saw autocad can use moltiple cores. How is this made. Is it using data paralelism or process paralelism and so on. Which rendering technics are used.
If you ever saw some data about that pleaselet me know
Domen
Dear Khoa i hope you are still there.
can you in few words explain me what this code is doing.
Using doc Dim workDb As Database = HostApplicationServices.WorkingDatabase HostApplicationServices.WorkingDatabase = doc.Database filePath = ServerFolder + imageFileName Dim vpn As Integer = System.Convert.ToInt32(Application.GetSystemVariable("CVPORT")) Dim gsv As GraphicsSystem.View = doc.GraphicsManager.GetGsView(vpn, True) Using view As GraphicsSystem.View = doc.GraphicsManager.v Using dev As Device = doc.GraphicsManager.CreateAutoCADOffScreenDevice() dev.DeviceRenderType = RendererType.FullRender dev.Add(view) Dim bitmap As Bitmap = view.RenderToImage() Using bitmap bitmap.Save(filePath) fileStream = File.OpenRead(filePath) _client.Result = fileStream End Using End Using End Using ' Restore the previous working database back HostApplicationServices.WorkingDatabase = workDb End Using
What is CVPORT system variable for? in my case it's 2 (i think this mean we have 3D scene am i right?)
After that we are prepering view for render, am i right?
Hi dj-nemo,
I will have a look again on your code around the end of my working day (US Central Time). Last week I was very busy and did not have time to track this .NET forum. I will be back to see this issue.
-Khoa
Can't find what you're looking for? Ask the community or share your knowledge.