Aligned dimension script in python 3

Hi All

Please take a look at what I thought was a simple script using rs.AddAlignedDimension when the script runs all 90 degree dims are ok as are the 45 and 135 degree but the 225 and 315 are not aligned but are square dimensions.

I have listed the script and attached model with the points I was using.
curves.3dm (159.5 KB)

#! python3
import Rhino
import Rhino.UI
import scriptcontext as sc
import System
import Eto.Drawing as drawing
import Eto.Forms as forms
import rhinoscriptsyntax as rs
from System.Drawing import Color
from decimal import Decimal
import math
pi = math.pi 

import operator

#surfaces = []


JobNum = []
#Plane = []
NewLayer = []
Font = []
#Tol = []
#Thk = []
#maxDist = []
#VecScale = []
ModScale = []
NumDp = []
Spoints = [] 
Names = []

number = 1

Origls = []

curves = []

#Geo Vectors Points dialog class
class GeoAssociateDialog(forms.Dialog[bool]):
    
    # Dialog box Class initializer
    def __init__(self):

        # call base class initializer
        super().__init__()

        # Initialize dialog box
        self.Title = "Geo Label Curve Length"
        self.Padding = drawing.Padding(10)
        self.Resizable = False
        
        
        # Create Radio Button List Control
        #self.m_radiobuttonlist = forms.RadioButtonList()
        #self.m_radiobuttonlist.DataStore = ['No Plane', 'Fit Plane']
        #self.m_radiobuttonlist.Orientation = forms.Orientation.Horizontal
        #self.m_radiobuttonlist.SelectedIndex = 0
        
        # Create controls for the dialog
        self.m_label1 = forms.Label()
        self.m_label1.Text = "Job Number:"


        self.m_JobNum_textbox = forms.TextBox()
        self.m_JobNum_textbox.Text = "SC-000"
        #self.m_JobNum_textbox = forms.TextBox(Text = "")
        
        self.m_label2 = forms.Label()
        self.m_label2.Text = "New Layer:"

        self.m_NewLayer_textbox = forms.TextBox()
        self.m_NewLayer_textbox.Text = "Curve Length"
        #self.m_NewLayer_textbox = forms.TextBox(Text = "")
        
        #self.m_label3 = forms.Label()
        #self.m_label3.Text = "maxDist mm:"

        #self.m_maxDist_textbox = forms.TextBox()
        #self.m_maxDist_textbox.Text = ""
        
        #self.m_label4 = forms.Label()
        #self.m_label4.Text = "Thickness mm:"


        #self.m_Thickness_textbox = forms.TextBox()
        #self.m_Thickness_textbox.Text = ""
        
        self.m_label5 = forms.Label()
        self.m_label5.Text = "Font Size mm:"

        self.m_Font_textbox = forms.TextBox()
        self.m_Font_textbox.Text = ""
        
        #self.m_label6 = forms.Label()
        #self.m_label6.Text = "Vector Scale:"

        #self.m_VectScale_textbox = forms.TextBox()
        #self.m_VectScale_textbox.Text = "3"
        
        #self.m_label7 = forms.Label()
        #self.m_label7.Text = "Tol mm:"


        #self.m_Tol_textbox = forms.TextBox()
        #self.m_Tol_textbox.Text = "8"        
        
        self.m_label8 = forms.Label()
        self.m_label8.Text = "Model Space Scale:"

        self.m_ModScale_textbox = forms.TextBox()
        self.m_ModScale_textbox.Text = "15"       
        
        
        self.m_NumDp_combobox = forms.ComboBox()
        self.m_NumDp_combobox.DataStore = ['NumDP0', 'NumDP1', 'NumDP2']
        self.m_NumDp_combobox.SelectedIndex = 0
        
        self.m_label = forms.Label()
        self.m_label.Text = "Decimal Places:"
        
        # Create the default button
        self.DefaultButton = forms.Button()
        self.DefaultButton.Text = 'OK'

        self.DefaultButton.Click += self.OnOKButtonClick
        
        # Create the abort button
        self.AbortButton = forms.Button()
        self.AbortButton.Text = 'Cancel'
        
        self.AbortButton.Click += self.OnCloseButtonClick
        
        
        # Create a table layout and add all the controls
        layout = forms.DynamicLayout()
        layout.Spacing = drawing.Size(5, 5)
        
        #layout.AddRow(self.m_JobNum_textbox)
        
        #layout.AddRow(self.m_label, self.m_radiobuttonlist)
        
        #layout.AddRow(self.m_label, self.m_Layers_listbox)
        #layout.AddRow(self.m_JobNum_textbox)
        layout.AddRow(self.m_label1, self.m_JobNum_textbox)
        #layout.AddRow(self.m_NewLayer_textbox)
        layout.AddRow(self.m_label2, self.m_NewLayer_textbox)
        
        #layout.AddRow(self.m_label3, self.m_maxDist_textbox)
        
        #layout.AddRow(self.m_label4, self.m_Thickness_textbox)
        
        layout.AddRow(self.m_label5, self.m_Font_textbox)
        
        #layout.AddRow(self.m_label6, self.m_VectScale_textbox)
        
        #layout.AddRow(self.m_label7, self.m_Tol_textbox)
        
        layout.AddRow(self.m_label8, self.m_ModScale_textbox)
        
        layout.AddRow(self.m_label, self.m_NumDp_combobox)
        
        layout.AddRow(None) # spacer
        
        layout.AddRow(self.DefaultButton, self.AbortButton)
        
        # Set the dialog content
        self.Content = layout
        self.Load += self.OnFormLoad
        self.Closing += self.OnFormClosing
        

    
    # Start of the class functions
    
    # Get the value of the textbox
    
    #def GetText20(self):
        #return self.m_Layers_listtbox.SelectedIndex
    
    def GetText1(self):
        return self.m_JobNum_textbox.Text
        
    def GetPlane(self):
        return self.self.m_radiobuttonlist.SelectedIndex
        
        
    def GetText2(self):
        return self.m_NewLayer_textbox.Text
        
    def GetText3(self):
        return self.m_maxDist_textbox.Text
        
    def GetText4(self):
        return self.m_Thickness_textbox.Text
        
    def GetText5(self):
        return self.m_Font_textbox.Text
        
    def GetText6(self):
        return self.m_VectScale_textbox.Text
        
    def GetText7(self):
        return self.m_Tol_textbox.Text

    def GetText8(self):
        return self.m_ModScale_textbox.Text

        
    # Get/set the value of the combobox
    def GetNumDp(self):
        return self.m_NumDp_combobox.SelectedIndex
    #def SetNumDp(self, index):
      #self.m_NumDp_combobox.SelectedIndex = Rhino.RhinoMath.Clamp(index, 0, 2)
    
    # Close button click handler
    def OnCloseButtonClick(self, sender, e):
        self.Close(False)
    
    # OK button click handler
    def OnOKButtonClick(self, sender, e):
        self.Close(True)
        
        
        
        
    def OnFormLoad(self, sender, e):
        # set previous or default text for "Job Number"
        if sc.sticky.has_key("Job Number"):
            # set last used text if found in sticky
            self.m_JobNum_textbox.Text = sc.sticky["Job Number"]
        else:
            # set default text if not not found in sticky
            self.m_JobNum_textbox.Text = "SC-000"
            
        #if sc.sticky.has_key("Plane"):
            #self.m_radiobuttonlist.SelectedIndex = sc.sticky["Plane"]
        #else:
            #self.m_radiobuttonlist.SelectedIndex = 0
            
        if sc.sticky.has_key("New Layer"):
            # set last used text if found in sticky
            self.m_NewLayer_textbox.Text = sc.sticky["New Layer"]
        else:
            # set default text if not not found in sticky
            self.m_NewLayer_textbox.Text = "Curve Length"
            
        #if sc.sticky.has_key("maxDist"):
            # set last used text if found in sticky
            #self.m_maxDist_textbox.Text = sc.sticky["maxDist"]
        #else:
            # set default text if not not found in sticky
            #self.m_maxDist_textbox.Text = "50"
            
        #if sc.sticky.has_key("Thickness"):
            # set last used text if found in sticky
            #self.m_Thickness_textbox.Text = sc.sticky["Thickness"]
        #else:
            # set default text if not not found in sticky
            #self.m_Thickness_textbox.Text = "0"
            
        if sc.sticky.has_key("Font Size"):
            # set last used text if found in sticky
            self.m_Font_textbox.Text = sc.sticky["Font Size"]
        else:
            # set default text if not not found in sticky
            self.m_Font_textbox.Text = "100"
            
        #if sc.sticky.has_key("Vect Scale"):
            # set last used text if found in sticky
            #self.m_VectScale_textbox.Text = sc.sticky["Vect Scale"]
        #else:
            # set default text if not not found in sticky
            #self.m_VectScale_textbox.Text = "3"
            
        #if sc.sticky.has_key("Tol"):
            # set last used text if found in sticky
            #self.m_Tol_textbox.Text = sc.sticky["Tol"]
        #else:
            # set default text if not not found in sticky
            #self.m_Tol_textbox.Text = "8"
       
        if sc.sticky.has_key("Model Space Scale"):
            # set last used text if found in sticky
            self.m_ModScale_textbox.Text = sc.sticky["Model Space Scale"]
        else:
            # set default text if not not found in sticky
            self.m_ModScale_textbox.Text = "1"
            
            
    
    def OnFormClosing(self, sender, e):
        # store the text from the text box in sticky (it can be empty)
        sc.sticky["Job Number"] = self.m_JobNum_textbox.Text
        
        #sc.sticky["Plane"] = self.m_radiobuttonlist.SelectedIndex
        # store the text from the text box in sticky (it can be empty)
        
        sc.sticky["New Layer"] = self.m_NewLayer_textbox.Text
        
        # store the text from the text box in sticky (it can be empty)
        #sc.sticky["maxDist"] = self.m_maxDist_textbox.Text
        
        # store the text from the text box in sticky (it can be empty)
        #sc.sticky["Thickness"] = self.m_Thickness_textbox.Text
        
        # store the text from the text box in sticky (it can be empty)
        sc.sticky["Font Size"] = self.m_Font_textbox.Text
        
        # store the text from the text box in sticky (it can be empty)
        #sc.sticky["Vect Scale"] = self.m_VectScale_textbox.Text
        
        # store the text from the text box in sticky (it can be empty)
        #sc.sticky["Tol"] = self.m_Tol_textbox.Text
        
       # store the text from the text box in sticky (it can be empty)
        sc.sticky["Model Space Scale"] = self.m_ModScale_textbox.Text
        
        
def RadialDimensions():
    
    # Utility function to show the dialog box
    # The script that will be using the dialog.
    dialog = GeoAssociateDialog();
    
    #Open the dialogue

    rc = dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow)
    if not rc: return
    
    #Plane = str(dialog.m_radiobuttonlist.SelectedIndex)
    #print ("Plane Fit", Plane)
    
    JobNum = str(dialog.m_JobNum_textbox.Text)
    print ("JobNum", JobNum)
    
    NewLayer = str(dialog.m_NewLayer_textbox.Text)
    print ("NewLayer", NewLayer)
    
    #maxDist = str(dialog.m_maxDist_textbox.Text)
    #print ("maxDist", maxDist)
    
    #Thk = str(dialog.m_Thickness_textbox.Text)
    #print ("Thickness", Thk)
    
    Font = int(dialog.m_Font_textbox.Text)
    print ("Font", Font)
    
    #VecScale = str(dialog.m_VectScale_textbox.Text)
    #print ("VecScale", VecScale)
    
    #Tol = str(dialog.m_Tol_textbox.Text)
    #print ("Tolerance to Design", Tol)
    
    ModScale = int(dialog.m_ModScale_textbox.Text)
    print ("Model Space Scale", ModScale)
    
    NumDp = int(dialog.m_NumDp_combobox.SelectedIndex)
    print ("Number of Decimal Places", NumDp)
        
    
    #surfaces = []
    
    rs.AddLayer(str(JobNum))
    rs.AddLayer(str(NewLayer),Color.Yellow, parent = str(JobNum))
    rs.CurrentLayer(str(NewLayer))

    
    #objs = rs.GetObjects("Pick some curves", rs.filter.curve)

    rs.CurrentDimStyle("Default")
    rs.DimStyleScale("Default", ModScale)
    #rs.ObjectLayer(obj, NewLayer)
    
    if NumDp == 0:
        #rs.CurrentDimStyle("Dp0")
        #rs.DimStyleScale("Dp0", modscale)
        rs.DimStyleLinearPrecision("Default", 0)
        #precision = 3
        #rs.UnitDistanceDisplayPrecision( 0 )

        
        
    elif NumDp == 1:
        
        #rs.CurrentDimStyle("Dp1")
        #rs.DimStyleScale("Dp1", modscale)
        rs.DimStyleLinearPrecision("Default", 1)
        #precision = 3
        #rs.UnitDistanceDisplayPrecision( 1 )
        
                        
    elif NumDp == 2:
        
        #rs.CurrentDimStyle("Dp2")
        #rs.DimStyleScale("Dp2", modscale)
        rs.DimStyleLinearPrecision("Default", 2)
        #precision = 3
        #rs.UnitDistanceDisplayPrecision( 2 )


    #print("Object identifier: {}".format(objectId))

    cpId = rs.GetObject("Pick the centre point", rs.filter.point)
    cpIdc = rs.PointCoordinates(cpId)
    if not cpIdc:
        return
        
        #print(cpIdc)

    rdIds = rs.GetObjects("Pick the radial points", rs.filter.point)
    #rdIds = rs.GetPointCoordinates("Pick the radial points")
    if not rdIds:
        return
        print(rdIds)


    rs.CurrentView("Top")


    if rdIds:
        for rdId in rdIds:

            rdIdc = rs.PointCoordinates(rdId)

            mid = rs.PointAdd(rdIdc, cpIdc)/2

            rs.AddPoint(mid)
             
            aDim = rs.AddAlignedDimension( cpIdc, rdIdc, mid, "Default" )

            #point = rs.CurveMidPoint(obj)



    print ("End of Radial Dimensions")
# Check to see if this file is being executed as the "main" python
# script instead of being used as a module by some other python script
# This allows us to use the module which ever way we want.
if __name__ == "__main__":

    rc = RadialDimensions()

Anyone able to give an answer on this issue ?

Hi Roger,

I can’t see anything obviously wrong in your code. That would lead me to suspect there is a bug in the RhinoCommon Rhino.Geometry.LinearDimension class constructor that ultimately gets called. However if you try manually drawing the Aligned Dimensions that fail in code they work OK (green in the following screenshot).

Also, while the code gives the wrong result going from centre to NW, if you go from SE to NW (i.e. twice the length but same direction) it works. (illustrated top right above).

This looks like one for McNeel - @dale maybe?

Regards
Jeremy

Hi Jeremy,

Thanks for taking a look I thought it was a bug but always good to have someone take a look in case I missed something obvious.

Roger

1 Like

Hi @RogerD,

Can you post a model that contains a line and and whatever annotation you’d like to create using a script?

Thanks,

– Dale

Hi Dale,

This is the radial dimensions bug that you may need to look at if you use the file I supplied Curves.3dm then run the script I posted at the start of this feedback. Select the centre point and then the radial points you will see that the dimensions do not give the radial dimension value as they should.

I think your reply above refers to my other question so will add some additional info to that and send it to you.

Roger

Radial dimension bug? I thought you were trying to create aligned dimensions?

I don’t see any dimensions in Curves.3dm. I want to see what you are trying to create.

– Dale

curves.3dm (173.9 KB)

Hi Dale

in this copy of curves.3dm you will see in plan view a circle with a red point at its centre and blue points around the circumference. If you run my script it should give the radial dimension from the red centre point to each of the blue points. Look at the values at 225 and 315 degrees they are not radial, like wise the 0 degree and 180 degree are not radial but the rest work out ok.

Roger

Dale

I had to use aligned dimensions with zero offset to give radial dimensions as I could not find an addradialdimension option in rhinoscriptsyntax only rs.AddAlignedDimension( origin, offset, point )

Roger

One more time…

Please, using Rhino command, annotation the circle in the way you wish to achieve using a script. I don’t care what your script is producing - I want to see what you’re looking to create.

– Dale

radial dims.3dm (189.3 KB)

final requirements

Hi @RogerD,

Let me know if this helps.

TestDimAligned.py (1.7 KB)

– Dale

Hi Dale,

So is the problem that the Rhinoscriptsyntax function AddAlignedDimension() [found in dimension.py] creates an invalid plane because it is being supplied with colinear points and then substitutes the view cplane which is not aligned with the two supplied end points?

def AddAlignedDimension(start_point, end_point, point_on_dimension_line, style=None):
    """Adds an aligned dimension object to the document. An aligned dimension
    is a linear dimension lined up with two points
    Parameters:
      start_point (point): first point of dimension
      end_point (point): second point of dimension
      point_on_dimension_line (point): location point of dimension line
      style (str, optional): name of dimension style
    Returns:
      guid: identifier of new dimension on success
      None: on error
    Example:
      import rhinoscriptsyntax as rs
      origin = 1, 1, 0
      offset = 11, 5, 0
      point = 1, 3, 0
      rs.AddAlignedDimension( origin, offset, point )
    See Also:
      IsAlignedDimension
    """
    start = rhutil.coerce3dpoint(start_point, True)
    end = rhutil.coerce3dpoint(end_point, True)
    onpoint = rhutil.coerce3dpoint(point_on_dimension_line, True)
>   plane = Rhino.Geometry.Plane(start, end, onpoint)
>   if not plane.IsValid:
>     # usually when points passed to ctor are colinear
>     plane = ViewCPlane()
    success, s, t = plane.ClosestParameter(start)
    start = Rhino.Geometry.Point2d(s,t)
    success, s, t = plane.ClosestParameter(end)
    end = Rhino.Geometry.Point2d(s,t)
    success, s, t = plane.ClosestParameter(onpoint)
    onpoint = Rhino.Geometry.Point2d(s,t)
    ldim = Rhino.Geometry.LinearDimension(plane, start, end, onpoint)
    ...etc

Should it be rewritten to create the aligned dimension via your approach, at least where colinear points are supplied?

Regards
Jeremy

Hi Dale,

Thank you for your solution it has allowed me to progress and complete my script, hope you resolve the rhinoscriptsyntax version.

Roger

Hi Jeremy,

You explained that far better than I could have.

Roger