ADN Open CIS
Сообщество программистов Autodesk в СНГ

14/04/2014

WPF Control для отображения штриховки поверхностей

Как-то я создавал контрол для отображения штриховки поверхностей и уже давным-давно забыл про него. Однако, мой бывший коллега, Александр Игнатович, поделился моими наработками с Джереми.

Сегодня я расскажу вам о WPF контроле для просмотра Revit’овских штриховок поверхностей. Контрол изначально был создан Виктором Чекалиным, а поделился с этим полезным контролом Александр Игнатович из Инвестиционной венчурной компании.

Это будет неплохим улучшением для надстройки по добавлению материалов.

Надстройка по добавлению материалов читает данные о материалах из Excel файла и создает соответствующий материал в Revit.

Входные данные включают наименования штриховки поверхностей и разреза.

С помощью нового контрола мы можем отобразить штриховку поверхностей прямо на форме и где мы выбреем материалы для импорта:

 

Для использования контрола в вашей надстройки необходимо выполнить два простых шага:

  • Скопировать XAML FillPatternViewerControlWpf.xaml в ваш проект
  • Добавить ссылку на FillPatternViewerControlWpf в вашем XAML коде.

Модуль просмотра штриховок в Visual Studio будет выглядеть вот так:

 

 

Ниже представлен XAML код для отображение списка материалов на скриншоте сверху.

Код - XML: [Выделить]
  1.   <GridViewColumn Header="Surface"
  2.                   Width="80">
  3.   
  4.     <GridViewColumn.CellTemplate>
  5.       <DataTemplate DataType
  6.           ="{x:Type ViewModel:MaterialViewModel}">
  7.   
  8.         <Border CornerRadius="2"
  9.                 BorderThickness="1"
  10.                 BorderBrush="Gray">
  11.   
  12.           <Controls:FillPatternViewerControlWpf
  13.             HorizontalAlignment="Stretch"
  14.             Height="30"
  15.             Margin="1,3"
  16.             FillPattern="{Binding SurfacePattern}"/>
  17.   
  18.         </Border>
  19.       </DataTemplate>
  20.     </GridViewColumn.CellTemplate>
  21.   </GridViewColumn>
  22.   
  23.   <GridViewColumn Header="Cut"
  24.                   Width="80">
  25.   
  26.     <GridViewColumn.CellTemplate>
  27.       <DataTemplate DataType
  28.           ="{x:Type ViewModel:MaterialViewModel}">
  29.   
  30.         <Border CornerRadius="2"
  31.                 BorderThickness="1"
  32.                 BorderBrush="Gray">
  33.   
  34.           <Controls:FillPatternViewerControlWpf
  35.             HorizontalAlignment="Stretch"
  36.             Height="30"
  37.             Margin="1,3"
  38.             FillPattern="{Binding CutPattern}"/>
  39.   
  40.         </Border>
  41.       </DataTemplate>
  42.     </GridViewColumn.CellTemplate>
  43.   </GridViewColumn>

 

Сам же контрол объявлен в xaml файле и в файле cs, описывающем поведение контрола.

XAML выглядит вот так:

Код - XML: [Выделить]
  1. <UserControl
  2.   x:Class="AddMaterials.View.Controls.FillPatternViewerControlWpf"
  3.   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4.   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5.   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  7.   xmlns:Converters="clr-namespace:AddMaterials.View.Converters"
  8.   mc:Ignorable="d"
  9.   d:DesignHeight="300"
  10.   d:DesignWidth="300">
  11.  
  12.   <Grid Name="_grid"
  13.         ToolTip="{Binding Path=FillPattern.Name,
  14.           RelativeSource={RelativeSource
  15.             AncestorType={x:Type UserControl}}}">
  16.  
  17.     <Grid.Resources>
  18.       <Converters:BitmapToImageSourceConverter
  19.         x:Key="BitmapToImageSourceConverter"/>
  20.     </Grid.Resources>
  21.  
  22.     <Image Source="{Binding Path=FillPatternImage,
  23.              RelativeSource={RelativeSource
  24.              AncestorType={x:Type UserControl}},                
  25.              Converter={StaticResource BitmapToImageSourceConverter}}"
  26.            Stretch="None"/>
  27.  
  28.   </Grid>
  29. </UserControl>

Вся же логика по созданию изображения штриховки происходит в коде CSharp в методе DrawFillPattern, в котором извлекаются и затем отрисовываются данные о штриховки поверхности.

Код - C#: [Выделить]
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics;
  4. using System.Drawing;
  5. using System.Linq;
  6. using System.Windows;
  7. using Autodesk.Revit.DB;
  8. using Image = System.Drawing.Image;
  9. using Matrix = System.Drawing.Drawing2D.Matrix;
  10. using Rectangle = System.Drawing.Rectangle;
  11.  
  12. namespace AddMaterials.View.Controls
  13. {
  14.   /// <summary>
  15.   /// Interaction logic for FillPatternViewerControlWpf.xaml
  16.   /// </summary>
  17.   public partial class FillPatternViewerControlWpf
  18.     : INotifyPropertyChanged
  19.   {
  20.     private const float Scale = 50;
  21.     private Bitmap fillPatternImg;
  22.  
  23.     public static readonly DependencyProperty
  24.       FillPatternProperty = DependencyProperty
  25.         .RegisterAttached( "FillPattern",
  26.           typeof( FillPattern ),
  27.           typeof( FillPatternViewerControlWpf ),
  28.           new UIPropertyMetadata( null,
  29.             OnFillPatternChanged ) );
  30.  
  31.     private static void OnFillPatternChanged(
  32.       DependencyObject d,
  33.       DependencyPropertyChangedEventArgs e )
  34.     {
  35.       var fillPatternViewerControl
  36.         = d as FillPatternViewerControlWpf;
  37.  
  38.       if( fillPatternViewerControl == null ) return;
  39.  
  40.       fillPatternViewerControl.OnPropertyChanged(
  41.         "FillPattern" );
  42.  
  43.       fillPatternViewerControl.CreateFillPatternImage();
  44.     }
  45.  
  46.     public FillPattern FillPattern
  47.     {
  48.       get
  49.       {
  50.         return (FillPattern) GetValue(
  51.           FillPatternProperty );
  52.       }
  53.       set
  54.       {
  55.         SetValue( FillPatternProperty, value );
  56.       }
  57.     }
  58.  
  59.     public FillPattern GetFillPattern(
  60.       DependencyObject obj )
  61.     {
  62.       return (FillPattern) obj.GetValue(
  63.         FillPatternProperty );
  64.     }
  65.  
  66.     public void SetFillPattern(
  67.       DependencyObject obj,
  68.       FillPattern value )
  69.     {
  70.       obj.SetValue( FillPatternProperty, value );
  71.     }
  72.  
  73.     public FillPatternViewerControlWpf()
  74.     {
  75.       InitializeComponent();
  76.     }
  77.  
  78.     public event PropertyChangedEventHandler
  79.       PropertyChanged;
  80.  
  81.     //[NotifyPropertyChangedInvocator]
  82.     protected virtual void OnPropertyChanged(
  83.       string propertyName )
  84.     {
  85.       var handler = PropertyChanged;
  86.       if( handler != null ) handler( this,
  87.         new PropertyChangedEventArgs( propertyName ) );
  88.     }
  89.  
  90.     public Image FillPatternImage
  91.     {
  92.       get
  93.       {
  94.         if( fillPatternImg == null )
  95.           CreateFillPatternImage();
  96.         return fillPatternImg;
  97.       }
  98.     }
  99.  
  100.     private void CreateFillPatternImage()
  101.     {
  102.       if( FillPattern == null )
  103.         return;
  104.  
  105.       var width =
  106.         ( ActualWidth == 0 ? Width : ActualWidth ) == 0
  107.           ? 100
  108.           : ( ActualWidth == 0 ? Width : ActualWidth );
  109.  
  110.       if( double.IsNaN( width ) )
  111.         width = 100;
  112.  
  113.       var height =
  114.           ( ActualHeight == 0 ? Height : ActualHeight ) == 0
  115.             ? 30
  116.             : ( ActualHeight == 0 ? Height : ActualHeight );
  117.  
  118.       if( double.IsNaN( height ) )
  119.         height = 30;
  120.  
  121.       fillPatternImg = new Bitmap(
  122.         (int) width, (int) height );
  123.  
  124.       using( var g = Graphics.FromImage(
  125.         fillPatternImg ) )
  126.       {
  127.         DrawFillPattern( g );
  128.       }
  129.  
  130.       OnPropertyChanged( "FillPatternImage" );
  131.     }
  132.  
  133.     private void DrawFillPattern( Graphics g )
  134.     {
  135.       float matrixScale;
  136.  
  137.       var fillPattern = FillPattern;
  138.  
  139.       if( fillPattern == null )
  140.         return;
  141.  
  142.       if( fillPattern.Target == FillPatternTarget.Model )
  143.         matrixScale = Scale;
  144.       else
  145.         matrixScale = Scale * 10;
  146.  
  147.       try
  148.       {
  149.         var width =
  150.         ( ActualWidth == 0 ? Width : ActualWidth ) == 0
  151.           ? 100
  152.           : ( ActualWidth == 0 ? Width : ActualWidth );
  153.  
  154.         if( double.IsNaN( width ) )
  155.           width = 100;
  156.  
  157.         var height =
  158.             ( ActualHeight == 0 ? Height : ActualHeight ) == 0
  159.               ? 30
  160.               : ( ActualHeight == 0 ? Height : ActualHeight );
  161.  
  162.         if( double.IsNaN( height ) )
  163.           height = 30;
  164.  
  165.         var rect = new Rectangle( 0, 0,
  166.           (int) width, (int) height );
  167.  
  168.         var centerX = ( rect.Left + rect.Left
  169.           + rect.Width ) / 2;
  170.  
  171.         var centerY = ( rect.Top + rect.Top
  172.           + rect.Height ) / 2;
  173.  
  174.         g.TranslateTransform( centerX, centerY );
  175.  
  176.         var rectF = new Rectangle( -1, -1, 2, 2 );
  177.  
  178.         g.FillRectangle( Brushes.Blue, rectF );
  179.  
  180.         g.ResetTransform();
  181.  
  182.         var fillGrids = fillPattern.GetFillGrids();
  183.  
  184.         Debug.Print( "FilPattern name: {0}",
  185.           fillPattern.Name );
  186.  
  187.         Debug.Print( "Grids count: {0}",
  188.           fillGrids.Count );
  189.  
  190.         foreach( var fillGrid in fillGrids )
  191.         {
  192.           var degreeAngle = (float) RadianToGradus(
  193.             fillGrid.Angle );
  194.  
  195.           Debug.Print( new string( '-', 100 ) );
  196.  
  197.           Debug.Print( "\tOrigin: U: {0} V:{1}",
  198.             fillGrid.Origin.U, fillGrid.Origin.V );
  199.  
  200.           Debug.Print( "\tOffset: {0}", fillGrid.Offset );
  201.           Debug.Print( "\tAngle: {0}", degreeAngle );
  202.           Debug.Print( "\tShift: {0}", fillGrid.Shift );
  203.  
  204.           var pen = new Pen( System.Drawing.Color.Black )
  205.           {
  206.             Width = 1f / matrixScale
  207.           };
  208.  
  209.           float dashLength = 1;
  210.  
  211.           var segments = fillGrid.GetSegments();
  212.  
  213.           if( segments.Count > 0 )
  214.           {
  215.             pen.DashPattern = segments
  216.                 .Select( Convert.ToSingle )
  217.                 .ToArray();
  218.  
  219.             Debug.Print( "\tSegments:" );
  220.  
  221.             foreach( var segment in segments )
  222.             {
  223.               Debug.Print( "\t\t{0}", segment );
  224.             }
  225.  
  226.             dashLength = pen.DashPattern.Sum();
  227.           }
  228.  
  229.           g.ResetTransform();
  230.  
  231.           var rotateMatrix = new Matrix();
  232.           rotateMatrix.Rotate( degreeAngle );
  233.  
  234.           var matrix = new Matrix( 1, 0, 0, -1,
  235.             centerX, centerY );
  236.  
  237.           matrix.Scale( matrixScale, matrixScale );
  238.  
  239.           matrix.Translate( (float) fillGrid.Origin.U,
  240.             (float) fillGrid.Origin.V );
  241.  
  242.           var backMatrix = matrix.Clone();
  243.           backMatrix.Multiply( rotateMatrix );
  244.           matrix.Multiply( rotateMatrix );
  245.  
  246.           bool first = true;
  247.           for( int i = 20; i > 0; i-- )
  248.           {
  249.             if( !first )
  250.             {
  251.               matrix.Translate( (float) fillGrid.Shift,
  252.                 (float) fillGrid.Offset );
  253.  
  254.               backMatrix.Translate( (float) fillGrid.Shift,
  255.                 -(float) fillGrid.Offset );
  256.             }
  257.             else
  258.             {
  259.               first = false;
  260.             }
  261.  
  262.             var offset = ( -10 ) * dashLength;
  263.             matrix.Translate( offset, 0 );
  264.             backMatrix.Translate( offset, 0 );
  265.  
  266.             g.Transform = matrix;
  267.  
  268.             g.DrawLine( pen, new PointF( 0, 0 ),
  269.               new PointF( 200, 0 ) );
  270.  
  271.             g.Transform = backMatrix;
  272.  
  273.             g.DrawLine( pen, new PointF( 0, 0 ),
  274.               new PointF( 200, 0 ) );
  275.           }
  276.         }
  277.       }
  278.       catch( Exception ex )
  279.       {
  280.         Debug.Print( ex.Message );
  281.       }
  282.     }
  283.  
  284.     private double RadianToGradus( double radian )
  285.     {
  286.       return radian * 180 / Math.PI;
  287.     }
  288.   }
  289. }

Полный код с проектом для Visual Studio находится на GitHub.

Версия надстройки, обсуждаемая в посте – 2014.0.0.2

Спасибо Виктору за реализацию и Александру за то, что поделился с нами.

Источникhttp://thebuildingcoder.typepad.com/blog/2014/04/wpf-fill-pattern-viewer-control.html

Обсуждение: http://adn-cis.org/forum/index.php?topic=681

Опубликовано 14.04.2014