Trying to set UserText to a grasshopper model object (GH_ModelObject)

Hello Michael, I sure did, but there was not a lot of options :


For example I tried to print Value but I just get the name of the Class

Console.WriteLine("modelObject.UserText[Case] : " + modelObject.UserText.Value); → prints modelObject.UserText[Case] : Grasshopper.Rhinoceros.ModelUserText

I wish we had access to th API doc even if it is in progress.

EDIT : I tried harder and found that I just had to add .Value in my code to make it work :

        try {
            foreach (GH_ModelObject.Attributes modelObject in objectsList.OfType<GH_ModelObject.Attributes>())
            {
                if (modelObject.UserText.Value.ContainsKey("Case"))
                {
                    Console.WriteLine("modelObject.UserText[Case] : " + modelObject.UserText["Case"]);
                }
            }
        }
         catch (Exception e) {
            Console.WriteLine("0groupGeoInTree : "+e.Message);
            errorMessages.Add("0groupGeoInTree : "+e.Message);
        }

Thank you Michael for the final push !

2 Likes

Ok Now @AndyPayne my next problem is to set the UserText

I had this :

    modelObject.Geometry = GH_Convert.ToGeometricGoo(p);
    Dictionary<string, string> userText = new Dictionary<string, string>();

    if (travel)
    {
        userText["gCode"] = "G0";
        userText["F"] = travelF.ToString();
    }
    else
    {
        userText["gCode"] = "G1";
        userText["F"] = millingF.ToString();
    }

    modelObject.UserText = userText;

so I tried this :

    modelObject.UserText.Value = userText;

but it is not that easy to set the UserText :

  1. Error running script S
    Exception: Compile Error
    Property or indexer ‘ModelUserText?.Value’ cannot be assigned to – it is read only (Error CS0200) rhinocode:///grasshopper/1/c910a9af-d0c7-4586-a2b1-d3b1e4f913e1/20a13585-090d-479f-8db7-24685eaf67ef:[112:5]

Thank You !

The point is replacing the whole set of existing key/value pairs should not be easy.
The idea is the components merge data on top of the existing but not deleting what other parts of the definition already assigned to the object.

Honestly I made it too difficult :sweat_smile:

This is the new syntax.

1 Like

Hello Kike,
thank you for your answer, I am still having some trouble and I do not see where is the problem
here is the error message :

Compile Error
Operator ‘+=’ cannot be applied to operands of type ‘ModelUserText?’ and ‘Dictionary<string, string>’ (Error CS0019) rhinocode:///grasshopper/1/c910a9af-d0c7-4586-a2b1-d3b1e4f913e1/55496573-fc3a-4fee-9652-fe10387ca67b:[108:5]

Here is my full code, the interesting part is at the beginning where there is the method makeModelObject that creates a ModelObject with the appropriate UserText from a point :

// Grasshopper Script Instance
//#! csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

using Rhino;
using Rhino.Geometry;
using Rhino.DocObjects;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;

public class Script_Instance : GH_ScriptInstance
{
    private GH_ModelObject makeModelObject (Point3d p, Boolean travel, double travelF, double millingF) {

        //METHOD that create a ModelObject of a Point3D with the appropriate userText
        // --> G1 if it is a milling point
        // --> G0 if it is a travel point

        //PARAMS
        //p : the geometry of the GH_ModelObject
        //travel : flag to specify if the point is a travel point
        //travelF : the feed rate of a travel point
        //millingF : the feed rate of a milling point

        //Console.WriteLine("p from makeModelObject" + p);

GH_ModelObject.Attributes modelObject = new GH_ModelObject.Attributes();
if (p != null)
{
    modelObject.Geometry = GH_Convert.ToGeometricGoo(p);
    Dictionary<string, string> newUserText = new Dictionary<string, string>();

    if (travel)
    {
        newUserText["gCode"] = "G0";
        newUserText["F"] = travelF.ToString();
    }
    else
    {
        newUserText["gCode"] = "G1";
        newUserText["F"] = millingF.ToString();
    }

    modelObject.UserText += newUserText;
}
else
{
    throw new Exception("The point we are trying to add is null !");
}
        return modelObject;

    }

    private void RunScript(System.Collections.Generic.IEnumerable<Rhino.Geometry.Curve> curves, Rhino.Geometry.Point3d entryPoint, double zElevation, double travelF, double millingF, double segmentLength, bool ramp, double rampRadius, bool millingStyle, System.Collections.Generic.IEnumerable<bool> posturalFlags, out object a, out object b, out object c, out object d, out object error)
    {
        // rampDivideAndChainCurves
        // METHOD that transforms a list of curve into a list of GH_ModelObject
        // that wrap points that come from a divide by length of the curves.
        // each curve ool posturalFlag, is divided its points are wrapped with userText into a GH_ModelObject.
        // each curve is 'chained to the next curve using travel points that are zElevation higher.

        //PARAMS
        //curves : a list of curves
        //entryPoint : the entry point (the origin of the table)
        //zElevation : the height for the travel points
        //travelF : the feed rate of a travel point
        //millingF : the feed rate of a milling point
        //segmentLength : the spacing between points
        //ramp : the presence of ramps in between curves
        //rampRadius : radius of thoses ramps
        //millingStyle : reverse or conventional
        //posturalFlags : a list of booleans mirroring the list of curves if the flag is true then no points will be outputed from the script

        //Console.WriteLine("objectsList.GetType()" + objectsList.GetType());
        // Create a new list to store the GH_ModelObjects
        List<GH_ModelObject> list = new List<GH_ModelObject>();
        Point3d bPoint = Point3d.Unset;
        Point3d cPoint = Point3d.Unset;
        Vector3d dVector = Vector3d.Unset;
        bool posturalCheck = true;

        Point3d currentEntryPoint = entryPoint;
        List<String> errorMessages = new List<String>();
        
        // Declare a variable to hold the centroid of the second curve
        //Point3d centroidOfSecondCurve = Point3d.Unset;

        try {

            if (posturalFlags == null) {
                posturalCheck = false;
            }

            if (curves != null && entryPoint != null) {
           
                int curveIndex = 0;
                // Iterate over the curves
                foreach (Curve curve in curves) {

                    // if the posturalCheck is false
                    // OR
                    // if the posturalFlag corresponding the currentCurve is false then we proceed with the code, if it is true we skip the curve
                    if (!posturalCheck || !posturalFlags.ToList()[curveIndex]) {

                        //Add a copy of the entry point (last point of the previous curve), shifted 20mm higher in the Z direction
                        Point3d elevatedEntryPoint = new Point3d(currentEntryPoint.X, currentEntryPoint.Y,  zElevation);
                        list.Add(makeModelObject(elevatedEntryPoint, true, travelF, millingF));

                        // the last point before the firstMillingPoint
                        Point3d travelPoint;
                        // the first point of each curve of the list
                        Point3d firstMillingPoint = curve.PointAtStart;


                        //if ramp we set the travelPoint as the arcFirstPoint to have an arc between this point and the firstMillingPoint
                        if (ramp) {

                            // Calculate the tangent vector and perpendicular vector at the first point of the curve
                            Vector3d tangent = curve.TangentAtStart;

                            //topRamp
                            tangent.Unitize();
                            bPoint = firstMillingPoint;
                            dVector = tangent;

                            // Calculate the offset point on the tangent "line"
                            Point3d offsetPoint = firstMillingPoint - (rampRadius * tangent);
                            cPoint = offsetPoint;

                            //we set the travelPoint as an elevation of the offsetPoint
                            travelPoint = new Point3d(offsetPoint.X, offsetPoint.Y, offsetPoint.Z + rampRadius);

                            // // Add a copy of the travelPoint, shifted 20mm higher in the Z direction
                            Point3d elevatedTravelPoint = new Point3d(travelPoint.X, travelPoint.Y, zElevation);
                            list.Add(makeModelObject(elevatedTravelPoint, true, travelF, millingF));

                            // //Add the travelPoint
                            list.Add(makeModelObject(travelPoint, true, travelF, millingF));

                            Vector3d vectorForTheArc = Vector3d.CrossProduct(tangent, Vector3d.XAxis);
                            vectorForTheArc.Unitize();

                            // Create the arc leading to the first point of the curve
                            Arc arc = new Arc();


                            // Create the arc leading to the first point of the curve
                            Arc arc1 = new Arc(travelPoint, -vectorForTheArc, firstMillingPoint);

                            // Create the arc leading to the first point of the curve
                            Arc arc2 = new Arc(travelPoint, vectorForTheArc, firstMillingPoint);

                            // Console.WriteLine("arc " + arc);
                            // Console.WriteLine("arc.IsValid " + arc.IsValid);

                            // if thr arc is valid we discretize it and add its points to the path
                            if (arc1.IsValid && arc2.IsValid) {

                                // Calculate the lengths of all three arcs
                                double arc1Length = arc1.Length;
                                double arc2Length = arc2.Length;

                                                                // Find the shortest arc
                                if (arc1Length < arc2Length)
                                {
                                    arc = arc1;
                                }
                                else if (arc2Length < arc1Length)
                                {
                                    arc = arc2;
                                }

                                //convert the arc to a curve
                                Curve arcCurve = arc.ToNurbsCurve();
                                // Console.WriteLine("arcCurve " + arcCurve);
                                // Console.WriteLine("arcCurve.GetLength() " + arcCurve.GetLength());

                                //we divide the arc and add its points as travel points
                                Rhino.Geometry.Point3d[] arcPoints;
                                arcCurve.DivideByLength(segmentLength, false, out arcPoints);

                                if (arcPoints != null)
                                {
                                    foreach (Rhino.Geometry.Point3d point in arcPoints) {
                                        //Console.WriteLine("rampPoint " + point);
                                        list.Add(makeModelObject(point, true, travelF, millingF));
                                    }
                                }
                            } else {
                                errorMessages.Add("0divideAndChainCurves : problem creating a ramp");
                            }

                        //if there is no ramp the travelPoint is the elevatedFirstMillingPoint
                        } else {

                            // Add a copy of the secondTrajectoryPoint, shifted 20mm higher in the Z direction
                            travelPoint = new Point3d(firstMillingPoint.X, firstMillingPoint.Y, zElevation);
                            list.Add(makeModelObject(travelPoint, true, travelF, millingF));
                            list.Add(makeModelObject(firstMillingPoint, false, travelF, millingF));
                        }

                        // Divide the curve by length

                    curve.TryGetPolyline(out Polyline pc);
                    
                        Rhino.Geometry.Point3d[] points = pc.ToArray();


                        //curve.DivideAsContour.DivideByLength(segmentLength, false, out points);
                        if (points != null) {
                            foreach (Rhino.Geometry.Point3d point in points) {
                                list.Add(makeModelObject(point, false, travelF, millingF));
                            }
                        }


                        // Add a copy of the last curve point
                        Point3d lastCurvePoint = curve.PointAtEnd;
                        list.Add(makeModelObject(lastCurvePoint, false, travelF, millingF));

                        //linking each curve to the next we set the current entrypoint to the last of the current curve
                        currentEntryPoint = curve.PointAtEnd; 
                    }
                    curveIndex++;
                }
            } else {
                Console.WriteLine("0divideAndChainCurves : curves or entryPoint is null !!");
                errorMessages.Add("0divideAndChainCurves : curves or entryPoint is null !!");
            }
        } catch (Exception e) {
            Console.WriteLine("0divideAndChainCurves : "+e.Message);
            errorMessages.Add("0divideAndChainCurves : "+e.Message);
        }

        // Assign the tree to the 'a' out parameter
        a = list;
        b = bPoint;
        c = cPoint;
        d = dVector;
        error = errorMessages;
    }
}

Also if I remove the GH_ prefix in front of ModelObject as in your code, here is the error i get :

Compile Error
The type or namespace name ‘ModelObject’ could not be found (are you missing a using directive or an assembly reference?) (Error CS0246)

Thank you for your lights.

Olivier

@roievil,

Those changes will be available next week. :pray:

2 Likes

Ok, it works, thank you

1 Like

Hello, I am testing my code on the latest rhino 8 (8.0.23304.9001, 2023-10-31), and it is once more broken, I get the following message for the following code

  {
    // Write your logic here
     DataTree<GH_ModelObject> geoTree = new DataTree<GH_ModelObject>();
    a = null;
  }

and

The type or namespace name 'GH_ModelObject' could not be found (are you missing a using directive or an assembly reference?)

There are also squiggly lines under the GH_ of GH_ModelObject

Does anyone know the workaround or the documentation I should look into? @AndyPayne @kike

Thank you,

Olivier

Hi @roievil,

The type GH_ModelObject is been renamed to simply ModelObject on the final build.

1 Like

Hello, thank you @kike for your answer, I had tried that but something was off and still is :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

using Rhino;
using Rhino.Geometry;

using Grasshopper.Rhinoceros.Model;
using System.Linq;

public class Script_Instance : GH_ScriptInstance
{
  /* 
    Members:
      RhinoDoc RhinoDocument
      GH_Document GrasshopperDocument
      IGH_Component Component
      int Iteration

    Methods (Virtual & overridable):
      Print(string text)
      Print(string format, params object[] args)
      Reflect(object obj)
      Reflect(object obj, string method_name)
  */
  
  private void RunScript(IEnumerable<object> objectsList, out object a)
  {
        //groupGeoInTree
        //METHOD that groups document objects in branches in a dataTree

        // Create a new list
        List<ModelObject> geoList = new List<ModelObject>();

        try {
            if (objectsList != null) {
                    // Count the number of objects belonging to the current group
                    int objectCount = objectsList.OfType<ModelObject>()
                        .Count();

                        Console.WriteLine("objectCount : ", objectCount);

                // Iterate through the objects in objectsList
                foreach (var obj in objectsList)
                {
                    if (obj is ModelObject)
                    {
                        geoList.Add((ModelObject)obj);
                    }
                    else
                    {
                        Console.WriteLine("0groupGeoInTree : Object is not of type ModelObject");
                    }
                }
            } 
        } catch (Exception e) {
            Console.WriteLine("0groupGeoInTree : "+e.Message);
        }
            // Assign the tree to the 'a' out parameter
            a = geoList;
    }
}

outputs

objectCount : 
0groupGeoInTree : Object is not of type ModelObject

given a simple circle in rhino

@roievil,

Data travelling along a Grasshopper wire is wrapped inside another type, the wrapper type manages Grasshopper related logic and the wrapped type is the actual meaningful data.

In case of points for instance those types are GH_Point and Point3d, in the model object case is ModelObject and ModelObject.Attributes, for layers would be ModelLayer and ModelLayer.Attributes.

The wrapper is shared across wires, so if a component output is connected to several other component inputs the same wrapper is used, but once the wrapper enters into a script component and because script components can potentially modify it, the data is automatically unwrapped and the contained type is exposed for the scripting component to modify if it needs to.

So same way you get a Brep you will get a ModelObject.Attributes inside the scripting components.

1 Like

Hello @kike, thank you for your answer it is very enlightening, so I changed ModelObject to ModelObject.Attributes inside my scripts and it works.

However In another part of my code I have a script that did create a Point3D with userText and I did not get how to make it work, ModelObject.Attributes does not have a Geometry property as GH_ModelObject did.

so

modelObject.Geometry = GH_Convert.ToGeometricGoo(p);

does not work anymore, having p a Point3D object.

original code :

    private ModelObject.Attributes makeModelObject (Point3d p, Boolean travel, double travelF, double millingF) {

        //METHOD that create a ModelObject of a Point3D with the appropriate userText
        // --> G1 if it is a milling point
        // --> G0 if it is a travel point

        //PARAMS
        //p : the geometry of the GH_ModelObject
        //travel : flag to specify if the point is a travel point
        //travelF : the feed rate of a travel point
        //millingF : the feed rate of a milling point

        //Console.WriteLine("p from makeModelObject" + p);



ModelObject.Attributes modelObject = new ModelObject.Attributes();
if (p != null)
{
    modelObject.Geometry = GH_Convert.ToGeometricGoo(p);
    
    Dictionary<string, string> newUserText = new Dictionary<string, string>();

    if (travel)
    {
        newUserText["gCode"] = "G0";
        newUserText["F"] = travelF.ToString();
    }
    else
    {
        newUserText["gCode"] = "G1";
        newUserText["F"] = millingF.ToString();
    }
    modelObject.UserText += newUserText;
}
else
{
    throw new Exception("The point we are trying to add is null !");
}

return modelObject;
}

Thank you,

Olivier

Hello @kike @AndyPayne, any news on the subject of how to script the creation of a rhino object with userText in the lastest version? I am stuck.

Thank you,

Olivier

Hi @roievil,

Right now there is no way to do this.
We will have to enhance scripting to enable this, hopefully during 8.1.

Thank you for your answer, that is not good news for me, do you have an idea on the timeshift of 8.1?

The plan is 8.1 is ready on December.

Ok, thank you for your answer, it’s a shame but I will do without that feature

Hello @kike

8.1 is here, and my script is broken again, can you tell me how I can assign userText and geometry to a modelObject?

Thank you,

Olivier

Hello @kike, what is happening? Can you tell me why I have the feeling you are going back and forth on the userText issue?
In the latest version to be able to read the UserText ModelObject.Attributes is not working anymore,I had to revert to ModelObject objects, I was not able to figure out how to create add userText and geometry to a ModelObject, How do I do that?

Thank you,

Olivier

Hi @roievil,

We had to move back in 8.1 to ModelObject instead of ModelObject.Attributes in order to enable Script components do dispatching for instance, without having to duplicate each Model Object for doing it.


Script-UserText.gh (23.6 KB)

The script source code contains this.

  private void RunScript(
	object Content,
	IEnumerable<string> Keys,
	IEnumerable<string> Values,
	out object Result)
  {
    Result = null;
    if (Content is ModelObject modelContent && Keys is object && Values is object)
    {
        var attributes = modelContent.ToAttributes();
        attributes.UserText += Keys.Zip(Values, (k, v) => new KeyValuePair<string, string>(k, v));
        Result = attributes;
    }    
  }

For creating new Model Object from other types of geometry please use this form.


Script-Points-UserText.gh (25.3 KB)

The script uses ModelObject.Cast to convert from Rhino Geometry types.

  private void RunScript(
	Point3d Point,
	IEnumerable<string> Keys,
	IEnumerable<string> Values,
	out object Result)
  {
    Result = null;
    if (ModelObject.Cast(Point) is ModelObject modelContent && Keys is object && Values is object)
    {
        var attributes = modelContent.ToAttributes();
        attributes.UserText += Keys.Zip(Values, (k, v) => new KeyValuePair<string, string>(k, v));
        Result = attributes;
    }    
  }
1 Like