locked
How would you do this in C# RRS feed

  • Question

  • I know how I'd do it in C++, but I'm old and would like younger minds to tell me how they'd do it in c#:

    Architecture of this problem in C#

    Basically an image and some settings are combined to create a g-code file version of the image. (g-code is used to control numerically controlled machines like lathes and milling machines).

    Should I have 4 classes? Or 4 structs? Or mixed? Should CreateGcodeVersion be a static function?

    All opinions welcome...


    http://www.ransen.com Cad and Graphics software

    Wednesday, May 25, 2016 5:20 AM

Answers

  • Thanks for the concrete example and text. Yes g-code is simply ASCII text.

    Image is the C# Image class I suppose, and Settings would be a class or struct of my own making?

    I've tried to understand why I should use the interface class, I've read a bit about them but can't see how they help me here.

    Could I not just use a static function which takes image and settings and returns a string?


    http://www.ransen.com Cad and Graphics software

    The interface has helped you, and all of us, already!  We have described the exchange of information between the consumer of the library and the implementation -- with valid C# code -- without yet committing to the details of the implementation.  It's the napkin sketch you provided in the image in your first post, but in C#.

    From here you can provide an implementation.

        class GCodeGenerator : IGCodeGenerator
        {
            public string GenerateGCode( System.Drawing.Image image, Properties.Settings settings )
            {
                Bitmap bm = new Bitmap( image );
    
                double startZ = settings.StartZ;
                double minZ = settings.MinZ;
                double maxZ = settings.MaxZ;
                double leftX = settings.LeftX;
                double rightX = settings.RightX;
                double topY = settings.TopY;
                double bottomY = settings.BottomY;
    
                StringBuilder sb = new StringBuilder();
                sb.AppendLine( "G21 (millimeters)" );
                sb.AppendLine( "G90 (absolute)" );
                sb.AppendLine( string.Format( "G92 X0 Y0 Z{0} (start at 0, 0, {0})", startZ ) );
                sb.AppendLine( string.Format( "G00 X{0} Y{1} Z{2}", leftX, topY, startZ ) );
    
                BitmapData bmdata = bm.LockBits( new Rectangle( 0, 0, bm.Width, bm.Height ), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb );
                const int bytesPerPixel = 4;
                double rangeZ = maxZ - minZ;
                double rangeX = rightX - leftX;
                double rangeY = bottomY - topY;
                unsafe {
                    byte* scan0 = (byte*)bmdata.Scan0.ToPointer();
                    for( int imagey = 0; imagey < bm.Height; ++imagey ) {
                        byte* line = scan0 + imagey * bmdata.Stride;
                        for( int u = 0; u < bm.Width; ++u ) {
                            int imagex = u;
                            if( imagey % 2 == 1 ) {
                                // every other line goes backwards.
                                imagex = bm.Width - 1 - u;
                            }
                            byte* pixel = line + bytesPerPixel * imagex;
                            byte val = pixel[1];
                            // scale z by green channel
                            double x = leftX + rangeX * imagex / (float)bm.Width;
                            double y = topY + rangeY * imagey / (float)bm.Height;
                            double z = minZ + rangeZ * (val / 255.0);
                            sb.AppendLine( string.Format( "G01 X{0} Y{1} Z{2} ({3})", x, y, z, val ) );
                        }
                    }
                }
    
                sb.AppendLine( string.Format( "G00 Z{0} (up to start)", startZ ) );
                sb.AppendLine( "G00 X0 Y0 (home)" );
                return sb.ToString();
            }
        }

    Could be called like this:

    IGCodeGenerator gcgen = new GCodeGenerator();
    string gcode = gcgen.GenerateGCode( Image.FromFile( filename ), Properties.Settings.Default );

    My quick example uses several double settings: { LeftX, RightX, TopY, BottomY, MinZ, MaxZ, StartZ}

    And I also changed the access of the IGCodeGenerator interface from public to internal based on the choice to leverage the existing Windows Forms Settings mechanism.

    *** DISCLAIMER ***

    I know nothing about G-Code.  Use this code at your own risk.  It may manufacture a terminator and destroy the world for all I know.

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 1:51 PM
  • I've tried to understand why I should use the interface class, I've read a bit about them but can't see how they help me here.

    Interfaces are related to abstract classes. And both are related to Inheritance and the Diamond Problem.

    Basically the C# designers use single inheritance to dodge that anoying crystall entirely.
    But that left us with a need. Interface sovles that.
    One way I keep picturing it is as a "purely abstract class". Normal abstract may or may not carry code. Interface can never.
    You can only inherit from one class (abstract or not). But you can implement as many interfaces as you want.

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 3:06 PM
  • I still think the explanation is pretty vague.

    There are two things.

    They're called Value and Settings.

    You process them.

    The output is something called G-Code.

    .

    Personally.

    I think far too much guesswork on intent is necessary to give a good answer.

    .

    You're best using instance classes whenever possible if you're doing any automated testing. For people planning on a future career in development it's advisable to practice automated testing techniques.

    So CreateGCodeVersion should probably be an instance class or a factory or something that has one main class and many injected. Or something like that.

    Depending on what it does.

    If you need to switch out functionality then an interface is a good thing.

    If you don't then it's possibly just a distraction.

    But say you had 3 different steps in a conversion process.

    You could define ISetup, IProcess, ITranslate.

    Each has a method.

    Different machines require differences for one or more of the steps.

    You can split out the steps and allow easy substitution of one or more steps.

    So MachineX has a SetupX : Isetup

    You inject an instance of SetupX and ProcessGeneric, TranslateGeneric it a processing class.

    It then calls methods on Setup, Process and Translate.

    .

    If Value is a complex object rather than a string, then that should be a class.

    If Settings is a complex object then that should be a class.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 6:22 PM
  • Thanks for the reply. But what do you mean by:

    "If you need to switch out functionality then an interface is a good thing."

    ?


    http://www.ransen.com Cad and Graphics software

    I mean substitute different processing, as in:

    You could define ISetup, IProcess, ITranslate.

    Each has a method.

    Different machines require differences for one or more of the steps.

    You can split out the steps and allow easy substitution of one or more steps.

    So MachineX has a SetupX : Isetup

    You inject an instance of SetupX and ProcessGeneric, TranslateGeneric it a processing class.

    It then calls methods on Setup, Process and Translate.

    .

    Say you're doing this for machine A you need one type of processing, for machine B a different type.

    You can have 2 classes which each have a DoStuff() method.

    They do different things.

    Make both implement the same iwhatever interface with a DoStuff method on it and you can cast either as an Iwhatever and call DoStuff:

    ((Iwhatever)thingummy).DoStuff();

    Or of course you can resolve out a dependency injection container or use a factory pattern.

    Alternatively, you can just expose a public Action from a class and set that to an anonymous or concrete action you supply as your factory builds your processing object.

    These are all valid ways to substitute processing.

    It's impossible to tell from the description whether that is necessary or not.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Wednesday, June 1, 2016 9:45 AM

All replies

  • Hi Owen,

    Thank you for posting here.

    I am not an expert on g-code language. As far as I know. If you want to use C# language on the machine. The machine should install .Net framework SDK. 
    From the link we know:

    The machine’s system requirements are Windows 7, Windows 7 Service Pack 1, Windows Server 2003 Service Pack 2, Windows Server 2008, Windows Server 2008 R2, Windows Server 2008 R2 SP1, Windows Vista Service Pack 1, Windows XP Service Pack 3.
    I am not sure whether your machine has these OS or not, If no, you will be not able to use C# language.

    Based on my search. I cannot find something else about how to convert C# to G language.

    Best Regards,

    Kristin


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    • Edited by DotNet Wang Wednesday, June 1, 2016 2:07 AM correct the paragrahp style
    Thursday, May 26, 2016 7:21 AM
  • I explained myself badly. I have C# installed. I know about g-code. I wondered if the architecture of my solution was correct, not the details. Should I use classes or functions or...?

    http://www.ransen.com Cad and Graphics software

    Monday, May 30, 2016 1:49 PM
  • I think this can be achieved by creating an Interface class for Image and Settings

    and a class which is implementing the above interfaces we can have the method/function to create the Gcode Version

    Regards 

    Swapnil

    Monday, May 30, 2016 2:02 PM
  • public interface IGCodeGenerator
    {
        GCode GenerateGCode( Image image, Settings settings );
    }

    Leverage existing classes where possible.  Not sure what format you want to use to transport your G-Code.  Perhaps it's just text?  In which case:

    public interface IGCodeGenerator
    {
        string GenerateGCode( Image image, Settings settings );
    }


    Monday, May 30, 2016 2:11 PM
  • Thanks for the concrete example and text. Yes g-code is simply ASCII text.

    Image is the C# Image class I suppose, and Settings would be a class or struct of my own making?

    I've tried to understand why I should use the interface class, I've read a bit about them but can't see how they help me here.

    Could I not just use a static function which takes image and settings and returns a string?


    http://www.ransen.com Cad and Graphics software

    Tuesday, May 31, 2016 7:02 AM
  • Thanks.

    Could you give me the skeleton of an example, if it is not too complicated...?


    http://www.ransen.com Cad and Graphics software

    Tuesday, May 31, 2016 7:03 AM
  • Thanks for the concrete example and text. Yes g-code is simply ASCII text.

    Image is the C# Image class I suppose, and Settings would be a class or struct of my own making?

    I've tried to understand why I should use the interface class, I've read a bit about them but can't see how they help me here.

    Could I not just use a static function which takes image and settings and returns a string?


    http://www.ransen.com Cad and Graphics software

    The interface has helped you, and all of us, already!  We have described the exchange of information between the consumer of the library and the implementation -- with valid C# code -- without yet committing to the details of the implementation.  It's the napkin sketch you provided in the image in your first post, but in C#.

    From here you can provide an implementation.

        class GCodeGenerator : IGCodeGenerator
        {
            public string GenerateGCode( System.Drawing.Image image, Properties.Settings settings )
            {
                Bitmap bm = new Bitmap( image );
    
                double startZ = settings.StartZ;
                double minZ = settings.MinZ;
                double maxZ = settings.MaxZ;
                double leftX = settings.LeftX;
                double rightX = settings.RightX;
                double topY = settings.TopY;
                double bottomY = settings.BottomY;
    
                StringBuilder sb = new StringBuilder();
                sb.AppendLine( "G21 (millimeters)" );
                sb.AppendLine( "G90 (absolute)" );
                sb.AppendLine( string.Format( "G92 X0 Y0 Z{0} (start at 0, 0, {0})", startZ ) );
                sb.AppendLine( string.Format( "G00 X{0} Y{1} Z{2}", leftX, topY, startZ ) );
    
                BitmapData bmdata = bm.LockBits( new Rectangle( 0, 0, bm.Width, bm.Height ), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb );
                const int bytesPerPixel = 4;
                double rangeZ = maxZ - minZ;
                double rangeX = rightX - leftX;
                double rangeY = bottomY - topY;
                unsafe {
                    byte* scan0 = (byte*)bmdata.Scan0.ToPointer();
                    for( int imagey = 0; imagey < bm.Height; ++imagey ) {
                        byte* line = scan0 + imagey * bmdata.Stride;
                        for( int u = 0; u < bm.Width; ++u ) {
                            int imagex = u;
                            if( imagey % 2 == 1 ) {
                                // every other line goes backwards.
                                imagex = bm.Width - 1 - u;
                            }
                            byte* pixel = line + bytesPerPixel * imagex;
                            byte val = pixel[1];
                            // scale z by green channel
                            double x = leftX + rangeX * imagex / (float)bm.Width;
                            double y = topY + rangeY * imagey / (float)bm.Height;
                            double z = minZ + rangeZ * (val / 255.0);
                            sb.AppendLine( string.Format( "G01 X{0} Y{1} Z{2} ({3})", x, y, z, val ) );
                        }
                    }
                }
    
                sb.AppendLine( string.Format( "G00 Z{0} (up to start)", startZ ) );
                sb.AppendLine( "G00 X0 Y0 (home)" );
                return sb.ToString();
            }
        }

    Could be called like this:

    IGCodeGenerator gcgen = new GCodeGenerator();
    string gcode = gcgen.GenerateGCode( Image.FromFile( filename ), Properties.Settings.Default );

    My quick example uses several double settings: { LeftX, RightX, TopY, BottomY, MinZ, MaxZ, StartZ}

    And I also changed the access of the IGCodeGenerator interface from public to internal based on the choice to leverage the existing Windows Forms Settings mechanism.

    *** DISCLAIMER ***

    I know nothing about G-Code.  Use this code at your own risk.  It may manufacture a terminator and destroy the world for all I know.

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 1:51 PM
  • I've tried to understand why I should use the interface class, I've read a bit about them but can't see how they help me here.

    Interfaces are related to abstract classes. And both are related to Inheritance and the Diamond Problem.

    Basically the C# designers use single inheritance to dodge that anoying crystall entirely.
    But that left us with a need. Interface sovles that.
    One way I keep picturing it is as a "purely abstract class". Normal abstract may or may not carry code. Interface can never.
    You can only inherit from one class (abstract or not). But you can implement as many interfaces as you want.

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 3:06 PM
  • Many thanks for that...you've given me lots to think about!

    http://www.ransen.com Cad and Graphics software

    Tuesday, May 31, 2016 5:00 PM
  • Thanks, I'll be thinking about this...!

    http://www.ransen.com Cad and Graphics software

    Tuesday, May 31, 2016 5:00 PM
  • I still think the explanation is pretty vague.

    There are two things.

    They're called Value and Settings.

    You process them.

    The output is something called G-Code.

    .

    Personally.

    I think far too much guesswork on intent is necessary to give a good answer.

    .

    You're best using instance classes whenever possible if you're doing any automated testing. For people planning on a future career in development it's advisable to practice automated testing techniques.

    So CreateGCodeVersion should probably be an instance class or a factory or something that has one main class and many injected. Or something like that.

    Depending on what it does.

    If you need to switch out functionality then an interface is a good thing.

    If you don't then it's possibly just a distraction.

    But say you had 3 different steps in a conversion process.

    You could define ISetup, IProcess, ITranslate.

    Each has a method.

    Different machines require differences for one or more of the steps.

    You can split out the steps and allow easy substitution of one or more steps.

    So MachineX has a SetupX : Isetup

    You inject an instance of SetupX and ProcessGeneric, TranslateGeneric it a processing class.

    It then calls methods on Setup, Process and Translate.

    .

    If Value is a complex object rather than a string, then that should be a class.

    If Settings is a complex object then that should be a class.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Tuesday, May 31, 2016 6:22 PM
  • Thanks for the reply. But what do you mean by:

    "If you need to switch out functionality then an interface is a good thing."

    ?


    http://www.ransen.com Cad and Graphics software

    Wednesday, June 1, 2016 4:39 AM
  • Thanks for the reply. But what do you mean by:

    "If you need to switch out functionality then an interface is a good thing."

    ?


    http://www.ransen.com Cad and Graphics software

    I mean substitute different processing, as in:

    You could define ISetup, IProcess, ITranslate.

    Each has a method.

    Different machines require differences for one or more of the steps.

    You can split out the steps and allow easy substitution of one or more steps.

    So MachineX has a SetupX : Isetup

    You inject an instance of SetupX and ProcessGeneric, TranslateGeneric it a processing class.

    It then calls methods on Setup, Process and Translate.

    .

    Say you're doing this for machine A you need one type of processing, for machine B a different type.

    You can have 2 classes which each have a DoStuff() method.

    They do different things.

    Make both implement the same iwhatever interface with a DoStuff method on it and you can cast either as an Iwhatever and call DoStuff:

    ((Iwhatever)thingummy).DoStuff();

    Or of course you can resolve out a dependency injection container or use a factory pattern.

    Alternatively, you can just expose a public Action from a class and set that to an anonymous or concrete action you supply as your factory builds your processing object.

    These are all valid ways to substitute processing.

    It's impossible to tell from the description whether that is necessary or not.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Proposed as answer by Albert_Zhang Thursday, June 2, 2016 4:41 PM
    • Marked as answer by Owen Ransen Thursday, June 2, 2016 5:06 PM
    Wednesday, June 1, 2016 9:45 AM
  • Thanks, a good explanation. I'm beginning to grok Interfaces,,,

    http://www.ransen.com Cad and Graphics software

    Wednesday, June 1, 2016 4:14 PM
  • Hi Owen Ransen,

    If some of above replies are helpful to you, you could mark them as answers to close this thread.

    Thank you for your support and understanding.

    Best Regard,

    Albert Zhang

    Thursday, June 2, 2016 4:43 PM