...

суббота, 25 апреля 2015 г.

Классификация предложений с помощью нейронных сетей без предварительной обработки

Довольно часто встречается задача классификации текстов — например, определение тональности (выражает ли текст позитивное мнение или отрицательное о чем-либо), или разнесения текста по тематикам. На Хабре уже есть хорошие статьи с введением в данный вопрос.

Сегодня я хочу поговорить о проблеме классификации отдельных предложений. Решение этой задачи позволяет делать много интересного, например, выделять положительные и отрицательные моменты из длинных текстов, определять тональность твитов, является компонентом многих систем отвечающих на естественно-языковые вопросы (классификация типа вопроса), помогает сегментировать веб-страницы на смысловые блоки и многое другое. Однако, классификация отдельных предложений значительно сложнее классификации больших блоков текста — в одном предложении значительно меньше полезных признаков, и велико влияние порядка слов. Например: «как положено фильму ужасов, этот фильм был ну очень жутким» — содержит негативные слова («ужас», «жуткий»), но выражает положительное мнение о фильме, «все было ужасно красиво», или даже «отличный фильм, ничего не скажешь, только зря деньги потратили».

Традиционно это затруднение стараются решать с помощью предварительной обработки текста и ручного выделения признаков. В предварительную обработку могут входить как относительно простые техники (учет отрицания путем приклеивания частицы «не» к следующим словам), там и более сложные наборы правил переключения тональности, а также построение дерева зависимостей, а в ручные признаки — словари положительных и отрицательных слов, место слова в предложении, и другие — насколько хватит фантазии. Ясно, что сей процесс нудный, требующий много сторонних функций (например, нужен парсер для грамматического разбора предложения, словари), и не всегда результативный. Например, если авторы предложений делали много грамматических ошибок, то парсер, строящий дерево зависимостей, начинает сильно путаться, и качество работы всей системы резко снижается.

Но дело даже и не в этом — а в том, что все это делать лень. Я глубоко убежден, что системы машинного обучения должны работать end-to-end – загрузили обучающие данные — получили работающую модель. Пусть даже качество упадет на пару процентов, но трудозатраты снизятся на порядок, и откроется путь для большого числа новых и полезных приложений (например, вот я нашел статью об интересном применении классификатора текстов ).

Теория: Но короче, от слов к делу. Чтобы предложение можно было подать на вход нейронной сети, надо решить несколько проблем. Во-первых, необходимо преобразовать слова в цифры. Первое желание, которое возникает — сопоставить каждому слову из словаря свое число. Скажем (Абрикос — 1, Аппарат — 2, …. Яблоко — 53845). Но делать так нельзя, потому что таким образом мы неявно предполагаем, что абрикос гораздо больше похож на аппарат, чем на яблоко. Второй вариант — закодировать слова длинным вектором, в котором нужному слову соответствует 1, а всем остальным — 0 (Абрикос — 1 0 0 …, Аппарат — 0 1 0 0 …, … Яблоко — … 0 0 0 1). Здесь все слова равноудалены и не похожи друг на друга. Этот подход гораздо лучше и в ряде случаев работает хорошо (если есть достаточно много примеров).

Но если набор примеров маленький, то весьма вероятно, что какие-то слова (например, «абрикос») в нем будут отсутствовать, и в результате встретив такие слова в реальных примерах, алгоритм не будет знать, что с ними делать. Поэтому оптимально кодировать слова такими векторами, чтобы похожие по смыслу слова оказывались близко друг к другу — а далекие, соответственно — далеко. Есть несколько алгоритмов, которые «читают» большие объемы текстов, и на основании этого создают такие вектора (самый известный, но не всегда самый лучший — word2vec). Подробности — тема для отдельного разговора, пока для понимания достаточно знать, что такие способы есть, они берут на вход длинные массивы текстов и выдают вектора фиксированной длины, соответствующие каждому слову.

Получив вектора слов, мы сталкиваемся с задачей номер два — как представить цельное предложение для нейронной сети. Дело в том, что обычные нейронные сети прямого распространения (feed-forward) должны иметь входные данные фиксированной длины (см картинку). Объяснять, как работает классическая искусственная нейронная сеть прямого распространения здесь не буду — на эту тему уже написано достаточно статей см, например. Для полноты картины вставлю только рисунок.

Так вот, наша проблема в том, что все предложения содержат разное число слов. Самый простой выход — сложить все вектора, получив таким образом результирующий вектор предложения. Приведя все такие вектора к единичной длине, получаем пригодные входные данные. Такое представление часто называется «neural bag of words” (NBoW) — «нейронная сумка слов», поскольку порядок слов в нем теряется. Плюсом данного алгоритма является крайняя простота реализации (имея под рукой вектора слов и любую библиотеку с реализацией нейронных сетей или другого классификатора, можно сделать рабочий вариант за 10 — 20 мин). При этом результаты иногда превосходят другие более сложные алгоритмы, оставаясь, правда, далеко от максимально возможных (сие, впрочем, зависит от задачи — например, при классификации текстов на отзывы о товарах/описания товаров/прочее, NBoW у нас показал 92% точности на тестовой выборке, против 86% алгоритма использующего логистическую регрессию и тщательно подобранный вручную словарь).

Однако, мы уже говорили о том, что для классификации предложений важен порядок слов. Чтобы сохранить порядок слов, вектор предложения можно образовать, соединив все вектора слов «голова к хвосту», в один длинный вектор, если конечно придумать, как передать нейронной сети входные данные разной длины.

Маленькое отступление — внимательный читатель должен возмутиться — я обещал классификатор предложений без предварительной обработки, но поделить предложение на слова и преобразовать в вектор — это уже предварительная обработка. Поэтому я должен был написать «с минимальной предварительной обработкой». Но это звучит не так хорошо, и к тому же по сравнению с обычными методами, такая обработка, в общем-то, и не считается. На сегодня есть методы, которые позволяют работать непосредственно с буквами, позволяя избежать деления предложения на слова и преобразования в вектора, но это опять отдельная тема.

Вернемся к проблеме разной длины входных данных. У нее есть разные решения, но мы пока рассмотрим одно — а именно сверточный фильтр. Идея простая — мы берем один нейрон и подаем на вход два (или более) слова (см. рис 2). Потом мы сдвигаем вход на одно слово и повторяем операцию. На выходе мы имеем представление предложения, которое в два (или в n) раз меньше оригинального. При этом таких фильтров обычно создается несколько (от 10 до 100). Далее операцию можно повторить, поставив над первым слоем, второй такой же, использующий входные значения первого пока все предложение не будет свернуто, либо, на определенном этапе выбрать максимальное значение активации нейрона (так называемый слой объединения — pooling layer). За счет этого, последний слой нейронов получает представление фиксированной длины, и уже он предсказывает нужную категорию предложения.

Главный результат — нейронная сеть получает возможность строить иерархические модели, формируя в каждом следующем слое более абстрактные представления предложения. Существует ряд модификаций, которые отличаются деталями архитектуры, при этом некоторые из них не только получают лучшие из известных результатов в классификации предложений на стандартных задачах, но и могут быть обучены для перевода с одного языка на другой.

Вообще такие сети похожи на те, которые успешно используются в распознавании изображений, что опять же приятно (одна архитектура на все задачи). Правда совсем одна не получается — у сетей обрабатывающих текст, все-таки есть свои особенности.

Опыты: Для опытов реализацию нейронной сети нам пришлось написать самостоятельно. Мы не хотели этого делать, но вышло так, что это проще, чем применить некоторые готовые библиотеки, которые к тому же сложно потом перенести в конечное приложение. Ну и полезно в плане самообразования. Тем более что данная реализация нам нужна не сама по себе (хотя и это полезно), а как часть большой системы.

В нашей тестовой реализации простой сверточной сети три слоя, — один сверточный слой, один слой объединения, и верхний полностью соединенный слой (как на первом рисунке), который выдает собственно классификацию. Все это следует примерно описанию системы из работы Kim et al, 2014 – там же есть и иллюстрация, которую я не буду копировать сюда, чтобы не думать про авторские права лишний раз.

В качестве объекта тестирования взяли стандартный набор положительных и отрицательных предложений о кинофильмах на английском языке. Вот что получилось:

Алгоритм Точность классификации
NBoW 68%
Сверточная сеть, 8 фильтров 74.3%
Сверточная сеть, 16 фильтров 77.8%

В целом, получилось достаточно неплохо. Лучший опубликованный результат на этих данных для таких сетей сейчас составляет 83% с 100 фильтрами (см. статью выше), а лучший результат с помощью ручного подбора признаков — 77.3%.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Рассказ о том, как написать свой собственный CSS препроцессор за 9 месяцев

Xочу рассказать о своем детище – препроцессоре и парсере CSS, которым я начал заниматься с апреля прошлого года. Зачем я начал заниматься им? Признаваясь себе честно уже сейчас, я могу сказать: хотелось изобрести свой собственный велосипед. Чем я руководствовался тогда? Трудно сказать. Возможно, тем же самым. А возможно, тем, что я толком не нашел ничего удовлетворяющего моим требованиям к CSS препроцессору для моей любимой платформы разработки.

Требования к CSS препроцессору у меня сформировались после прочтения одной из статей здесь. Это была статья про препроцессор «Stylus для Node.js». Собственно, тогда то я про эти «препроцессоры» и узнал. Меня поразила вся простота синтаксиса этого препроцессора. После двухдневного (а может и меньшего) просмотра результатов с гугла, я ничего интересного для себя не нашел. Вот именно в этот момент ко мне в голову и пришла шальная мысль: а почему бы нет?
Требования у меня были следующие:
  • Наиболее простой синтаксис (ну это само собой!)
  • Язык разработки – PHP (ну теперь то уже можно и сказать)
  • Возможность отображения исходного файла стилей в виде дерева блоков

Собственно, наверно, это и все.

Что у меня получилось?


Все это время, прошедшее с начальной точки отсчета, я потратил на разработку своего продукта, и у меня получилось следующее — библиотека MySheet с открытым исходным кодом, являющаяся одновременно и парсером, и препроцессором стилей CSS.

Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?). Несколько интересных моментов процесса разработки – о том, как я создавал свою библиотеку, с какими подводными камнями столкнулся и что я узнал в процессе ее создания – я расскажу чуть позже. А теперь, о возможностях моего CSS препроцессора.

  • Арифметические выражения
    Данная возможность есть практически в каждом CSS препроцессоре. Не обошла она стороной и библиотеку MySheet:
    $wrapper_height = 50%
    .wrapper
        height $wrapper_height + 20px
        top ($wrapper_height / 2)
    
    

    Для чего она нужна? Да Бог его знает. Шутка. Сейчас эта функция моей библиотеки находится в очень сыром виде. Нет типа bool, а следовательно пока что (пока что!) нет условий. Но есть одна приятная плюшка, применение которой вы можете найти на главной странице моего сайта:
    .object
        color #a50c5b - 50sat /* decrease saturation by 50% */
        background-color #a50c5b + 50lt /* make color lighter by 50 percent */
    
    

    Да! Это именно то, о чем вы сейчас думаете! Можно выполнять арифметические действия над цветами! Я надеюсь, вы вдоволь поиграетесь с этой фишкой на официальном сайте библиотеки, а теперь перейдем к следующему пункту в нашем списке.
  • Mixin’ы
    Ну, вот, не смог я написать это слово на русском. Уж больно многие пишут его на английском:
    @mixin filter-grayscale(percent)
        -webkit-filter: grayscale($percent);
        -ms-filter: grayscale($percent);
        -o-filter: grayscale($percent);
        filter: grayscale($percent);
    
    img
        filter-grayscale 100%
    img:hover
        filter-grayscale 0%
    
    

    Собственно, это уже априори стандартная функция всех препроцессоров. Пользоваться ей очень просто: нужно написать имя миксина вместо имени CSS-правила, а затем передать аргументы в значении данного правила.

    В объявлении миксина также доступна переменная $arguments, которая просто перечислит все переданные аргументы в одну строку:

    @mixin border-radius(topleft, topright)
        -webkit-border-radius $topleft $topright 4px 5px
        border-radius $arguments
    
    

    Я постарался, чтобы в препроцессор были уже встроены некоторые миксины. Это, во-первых, благоприятно скажется на производительности. Во-вторых, позволит разработчикам и дизайнерам сосредоточиться на написании стилей для сайта, а не методов для упрощения этого процесса. Плюс, второе часто бывает делать лень.
  • Функции
    Функций в библиотеки пока что всего – три. Это – abs, negate и unitless. Но база для их написания подготовлена, и в будущем список доступных функций будет расширяться.

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

  • Флаги
    Эту возможность я придумал относительно недавно. Смысл ее – простой до безобразия, и я думаю лучше показать сразу пример ее использования:
    html
        height 0
        width 50px !prefixWith(ms, moz) !important
        border-radius 5px !important
        filter-grayscale 50%
        transform scale(2)
    
    

    Флаг в данном случае применяется для удобного добавления префиксов к правилам и компилируется в следующий код CSS:
    html {
        height: 0;
        -ms-width: 50px !important;
        -moz-width: 50px !important;
        width: 50px !important;
        -moz-border-radius: 5px;
        -webkit-border-radius: 5px;
        border-radius: 5px;
        -webkit-filter: grayscale(50%);
        filter: grayscale(50%);
        -ms-transform: scale(2);
        -moz-transform: scale(2);
        -o-transform: scale(2);
        -webkit-transform: scale(2);
        transform: scale(2)
    }
    
    

    Флаг выполняет некоторые действия над конкретным правилом. У меня есть идея использовать флаги еще и для того, чтобы помечать и наделять правила определенными свойствами. Например, так можно сделать флаг !noMixin, который запретит компиляцию и вставку миксина в код CSS. Таким образом можно избежать расширения синтаксиса лишними символами и ключевыми словами.
  • Плагины
    Я старался сделать свою библиотеку расширяемой. Т.е. чтобы любой человек (и Вы, и я, и, вообще, любая домохозяйка) могли расширить возможности библиотеки написанием плагина, а не созданием форка на гитхаб и переворачиванием всего исходного кода (хотя второе я и не воспрещаю делать). Сейчас написано два плагина для библиотеки MySheet:
    — PluginMixin — добавляет возможность использования миксинов в коде MSS (MySheet Styles)
    — PluginSelectorExtensions — добавляет вкусняшки вроде: обращение к родительскому селектору (или группе селекторов) через символ & и псевдо-селектор :any(), который я нагло содрал с препроцессора CSSCrush.
  • Дерево блоков
    Это, собственно, и есть третье по списку требование, о котором я писал в начале статьи. Я хотел, чтобы стилями CSS можно было управлять не только непосредственно, изменяя исходники вручную, но и делать это из бэкэнда, т.е. производить те манипуляции с кодом, которые присущи CSS парсерам (тык и еще тык). Что это дает? Например, можно изменять стили сайта на основании предпочтений пользователя. А предпочтения могут быть самыми разными. Одни хотят шрифт больше, другие – передвинуть заголовок на главной страницы немного ниже. Данную возможность можно добавлять к различным генераторам сайтов, что благоприятно скажется на аудитории пользователей программного продукта.

    Переходя от слов к делу, хочется показать, какое именно дерево блоков образуется на выходе после парсинга файла с помощью библиотеки MySheet. Рассмотрим такой простой исходный файл MSS:

    html { color red; text-align: center; margin: 0 auto; }
    @mixin rounded-corners (top, right, bottom, left)
        -webkit-border-radius \$left + \$right \$top + \$bottom
        -moz-border-radius \$arguments
        border-radius \$arguments \$left \$right \$left \$right
            
    @mixin diagonal-border-radius(left, right)
        border-radius \$arguments \$right \$left
            
    @page 
        padding 5px
    body
        rounded-corners 1 2 3 4 
        .wrapper
            diagonal-border-radius 6px 10px
            h1 span
                color blue
    
    

    Пропустим этот код через парсер:
    <?php
    try {
        $result = $mysheet->parseCode($code); 
        $compiledCode = $result->toRealCss();
    } catch (\MSSLib\Error\MySheetException $ex) {
        echo($ex->getTraceAsString());
    }
    
    

    и на выходе получим примерно следующее дерево:
    Дерево блоков MSS
    object(MSSLib\Structure\Document)[64]
      protected '_docFilePath' => null
      protected 'children' => 
        array (size=5)
          0 => 
            object(MSSLib\Structure\Ruleset)[98]
              private '_selectors' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Selector)[99]
                      private '_mssPath' => string 'html' (length=4)
                      private '_cssPathGroup' => 
                        object(MSSLib\Structure\CssSelectorGroup)[180]
                          private 'paths' => 
                            array (size=1)
                              0 => string 'html' (length=4)
                      private '_ruleset' => 
                        &object(MSSLib\Structure\Ruleset)[98]
                      private '_isFullSelector' => null
                      private '_isParsed' => boolean true
                      private '_handlerMap' => null
              protected '_parentRuleset' => null
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[100]
                      private 'ruleName' => string 'color' (length=5)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[101]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\ColorClass)[104]
                                  protected 'type' => string 'html' (length=4)
                                  protected 'color' => 
                                    array (size=1)
                                      0 => string 'red' (length=3)
                                  protected '_colorLib' => null
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[100]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[102]
                      private 'ruleName' => string 'text-align' (length=10)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[103]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[107]
                                  protected 'text' => string 'center' (length=6)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[102]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Declaration)[105]
                      private 'ruleName' => string 'margin' (length=6)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[106]
                          private 'params' => 
                            array (size=2)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[110]
                                  protected 'metric' => float 0
                                  protected 'unit' => null
                              1 => 
                                object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[111]
                                  protected 'text' => string 'auto' (length=4)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[105]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
          1 => 
            object(MSSLib\Plugins\Mixin\Mixin)[97]
              protected 'name' => string 'rounded-corners' (length=15)
              protected 'locals' => 
                array (size=4)
                  0 => string 'top' (length=3)
                  1 => string 'right' (length=5)
                  2 => string 'bottom' (length=6)
                  3 => string 'left' (length=4)
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[109]
                      private 'ruleName' => string '-webkit-border-radius' (length=21)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[113]
                          private 'params' => 
                            array (size=2)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MathExprClass)[122]
                                  protected 'expressionTree' => 
                                    object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                      private 'value' (Tree\Node\Node) => null
                                      private 'parent' (Tree\Node\Node) => null
                                      private 'children' (Tree\Node\Node) => 
                                        array (size=3)
                                          0 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[117]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[119]
                                                  private 'varName' => string 'left' (length=4)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          1 => 
                                            object(MSSLib\Essentials\ExpressionTree\OperatorNode)[118]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\Operators\PlusOperator)[120]
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          2 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[121]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[123]
                                                  private 'varName' => string 'right' (length=5)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                              1 => 
                                object(MSSLib\EmbeddedClasses\MathExprClass)[130]
                                  protected 'expressionTree' => 
                                    object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                      private 'value' (Tree\Node\Node) => null
                                      private 'parent' (Tree\Node\Node) => null
                                      private 'children' (Tree\Node\Node) => 
                                        array (size=3)
                                          0 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[125]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[127]
                                                  private 'varName' => string 'top' (length=3)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          1 => 
                                            object(MSSLib\Essentials\ExpressionTree\OperatorNode)[126]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\Operators\PlusOperator)[128]
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          2 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[129]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[131]
                                                  private 'varName' => string 'bottom' (length=6)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[109]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[114]
                      private 'ruleName' => string '-moz-border-radius' (length=18)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[115]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[134]
                                  private 'varName' => string 'arguments' (length=9)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[114]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Declaration)[132]
                      private 'ruleName' => string 'border-radius' (length=13)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[133]
                          private 'params' => 
                            array (size=5)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[137]
                                  private 'varName' => string 'arguments' (length=9)
                              1 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[138]
                                  private 'varName' => string 'left' (length=4)
                              2 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[139]
                                  private 'varName' => string 'right' (length=5)
                              3 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[140]
                                  private 'varName' => string 'left' (length=4)
                              4 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[141]
                                  private 'varName' => string 'right' (length=5)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[132]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
              protected 'plugin' => 
                object(MSSLib\Plugins\Mixin\PluginMixin)[58]
                  private '_registeredMixins' => 
                    array (size=0)
                      empty
                  private '_systemMixins' => 
                    array (size=3)
                      'border-radius' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'border_radius' (length=13)
                      'transform' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'transform' (length=9)
                      'filter-grayscale' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'filter_grayscale' (length=16)
                  protected '_enabledMixinSetClasses' => 
                    array (size=1)
                      0 => string 'basic' (length=5)
          2 => 
            object(MSSLib\Plugins\Mixin\Mixin)[112]
              protected 'name' => string 'diagonal-border-radius' (length=22)
              protected 'locals' => 
                array (size=2)
                  0 => string 'left' (length=4)
                  1 => string 'right' (length=5)
              protected 'children' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Declaration)[136]
                      private 'ruleName' => string 'border-radius' (length=13)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[142]
                          private 'params' => 
                            array (size=3)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[145]
                                  private 'varName' => string 'arguments' (length=9)
                              1 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[146]
                                  private 'varName' => string 'right' (length=5)
                              2 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[147]
                                  private 'varName' => string 'left' (length=4)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[136]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
              protected 'plugin' => 
                object(MSSLib\Plugins\Mixin\PluginMixin)[58]
                  private '_registeredMixins' => 
                    array (size=0)
                      empty
                  private '_systemMixins' => 
                    array (size=3)
                      'border-radius' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'border_radius' (length=13)
                      'transform' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'transform' (length=9)
                      'filter-grayscale' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'filter_grayscale' (length=16)
                  protected '_enabledMixinSetClasses' => 
                    array (size=1)
                      0 => string 'basic' (length=5)
          3 => 
            object(MSSLib\Structure\AtRule)[135]
              protected '_name' => string 'page' (length=4)
              protected '_parameters' => string '' (length=0)
              protected 'children' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Declaration)[143]
                      private 'ruleName' => string 'padding' (length=7)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[148]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[151]
                                  protected 'metric' => float 5
                                  protected 'unit' => string 'px' (length=2)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[143]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => 
                        &object(MSSLib\Structure\AtRule)[135]
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
          4 => 
            object(MSSLib\Structure\Ruleset)[144]
              private '_selectors' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Selector)[150]
                      private '_mssPath' => string 'body' (length=4)
                      private '_cssPathGroup' => 
                        object(MSSLib\Structure\CssSelectorGroup)[96]
                          private 'paths' => 
                            array (size=1)
                              0 => string 'body' (length=4)
                      private '_ruleset' => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      private '_isFullSelector' => null
                      private '_isParsed' => boolean true
                      private '_handlerMap' => null
              protected '_parentRuleset' => null
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[152]
                      private 'ruleName' => string 'rounded-corners' (length=15)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[153]
                          private 'params' => 
                            array (size=4)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[156]
                                  protected 'metric' => float 1
                                  protected 'unit' => null
                              1 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[157]
                                  protected 'metric' => float 2
                                  protected 'unit' => null
                              2 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[158]
                                  protected 'metric' => float 3
                                  protected 'unit' => null
                              3 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[159]
                                  protected 'metric' => float 4
                                  protected 'unit' => null
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[152]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[154]
                      private 'ruleName' => string 'transform' (length=9)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[155]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\FunctionClass)[167]
                                  protected 'name' => string 'rotate' (length=6)
                                  protected 'arguments' => 
                                    array (size=1)
                                      0 => 
                                        object(MSSLib\EmbeddedClasses\MathExprClass)[174]
                                          protected 'expressionTree' => 
                                            object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                              private 'value' (Tree\Node\Node) => null
                                              private 'parent' (Tree\Node\Node) => null
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=2)
                                                  0 => 
                                                    object(MSSLib\Essentials\ExpressionTree\OperatorNode)[171]
                                                      private 'value' (Tree\Node\Node) => 
                                                        object(MSSLib\Operators\UnaryMinusOperator)[172]
                                                      private 'parent' (Tree\Node\Node) => 
                                                        &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                                      private 'children' (Tree\Node\Node) => 
                                                        array (size=0)
                                                          empty
                                                  1 => 
                                                    object(MSSLib\Essentials\ExpressionTree\ParamNode)[173]
                                                      private 'value' (Tree\Node\Node) => 
                                                        object(MSSLib\EmbeddedClasses\MetricClass)[175]
                                                          protected 'metric' => float 5
                                                          protected 'unit' => string 'deg' (length=3)
                                                      private 'parent' (Tree\Node\Node) => 
                                                        &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                                      private 'children' (Tree\Node\Node) => 
                                                        array (size=0)
                                                          empty
                                  protected '_functionRenderer' => 
                                    object(MSSLib\Essentials\FunctionRenderers\DefaultFunctionRenderer)[166]
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[154]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Ruleset)[149]
                      private '_selectors' => 
                        array (size=1)
                          0 => 
                            object(MSSLib\Structure\Selector)[161]
                              private '_mssPath' => string '.wrapper' (length=8)
                              private '_cssPathGroup' => 
                                object(MSSLib\Structure\CssSelectorGroup)[176]
                                  private 'paths' => 
                                    array (size=1)
                                      0 => string 'body .wrapper' (length=13)
                              private '_ruleset' => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              private '_isFullSelector' => null
                              private '_isParsed' => boolean true
                              private '_handlerMap' => null
                      protected '_parentRuleset' => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      protected 'children' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Structure\Declaration)[168]
                              private 'ruleName' => string 'diagonal-border-radius' (length=22)
                              private 'ruleValue' => 
                                object(MSSLib\Structure\RuleValue)[169]
                                  private 'params' => 
                                    array (size=2)
                                      0 => 
                                        object(MSSLib\EmbeddedClasses\MetricClass)[178]
                                          protected 'metric' => float 6
                                          protected 'unit' => string 'px' (length=2)
                                      1 => 
                                        object(MSSLib\EmbeddedClasses\MetricClass)[179]
                                          protected 'metric' => float 10
                                          protected 'unit' => string 'px' (length=2)
                                  private '_parentDeclaration' => 
                                    &object(MSSLib\Structure\Declaration)[168]
                                  private '_flags' => 
                                    array (size=0)
                                      empty
                              private 'ruleEnabled' => boolean true
                              private 'parent' (MSSLib\Structure\Block) => null
                              private '_handlerMap' => null
                              private '_handlerMap' (MSSLib\Structure\Block) => null
                          1 => 
                            object(MSSLib\Structure\Ruleset)[164]
                              private '_selectors' => 
                                array (size=1)
                                  0 => 
                                    object(MSSLib\Structure\Selector)[177]
                                      private '_mssPath' => string 'h1 span' (length=7)
                                      private '_cssPathGroup' => 
                                        object(MSSLib\Structure\CssSelectorGroup)[183]
                                          private 'paths' => 
                                            array (size=1)
                                              0 => string 'body .wrapper h1 span' (length=21)
                                      private '_ruleset' => 
                                        &object(MSSLib\Structure\Ruleset)[164]
                                      private '_isFullSelector' => null
                                      private '_isParsed' => boolean true
                                      private '_handlerMap' => null
                              protected '_parentRuleset' => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              protected 'children' => 
                                array (size=1)
                                  0 => 
                                    object(MSSLib\Structure\Declaration)[181]
                                      private 'ruleName' => string 'color' (length=5)
                                      private 'ruleValue' => 
                                        object(MSSLib\Structure\RuleValue)[182]
                                          private 'params' => 
                                            array (size=1)
                                              0 => 
                                                object(MSSLib\EmbeddedClasses\ColorClass)[185]
                                                  protected 'type' => string 'html' (length=4)
                                                  protected 'color' => 
                                                    array (size=1)
                                                      0 => string 'blue' (length=4)
                                                  protected '_colorLib' => null
                                          private '_parentDeclaration' => 
                                            &object(MSSLib\Structure\Declaration)[181]
                                          private '_flags' => 
                                            array (size=0)
                                              empty
                                      private 'ruleEnabled' => boolean true
                                      private 'parent' (MSSLib\Structure\Block) => null
                                      private '_handlerMap' => null
                                      private '_handlerMap' (MSSLib\Structure\Block) => null
                              private 'parent' (MSSLib\Structure\Block) => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              private '_handlerMap' (MSSLib\Structure\Block) => null
                      private 'parent' (MSSLib\Structure\Block) => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
      private 'parent' (MSSLib\Structure\Block) => null
      private '_handlerMap' (MSSLib\Structure\Block) => null
    


    Что в конечном итоге компилируется в следующий CSS-код:
    html {
        color: #ff0000;
        text-align: center;
        margin: 0 auto
    }
    
    @page  {
        padding: 5px
    }
    
    body {
        -webkit-border-radius: 6 4;
        -moz-border-radius: 1 2 3 4;
        border-radius: 1 2 3 4 4 2 4 2;
        -ms-transform: rotate(-5deg);
        -moz-transform: rotate(-5deg);
        -o-transform: rotate(-5deg);
        -webkit-transform: rotate(-5deg);
        transform: rotate(-5deg)
    }
    
    body .wrapper {
        border-radius: 6px 10px 10px 6px
    }
    
    body .wrapper h1 span {
        color: #0000ff
    }
    
    

  • Формат вывода CSS
    Форматом выходного CSS-кода можно управлять с помощью настроек библиотеки. Для этого следует задать предпочтительные префиксы и суффиксы к строкам:
    $mysheet = MySheet::Instance();
    $mysheet->setActiveDirectory(realpath('./'));
    $mysheet->getAutoload()->registerAutoload();
    $settings = new MSSettings();
    $settings->set('cssRenderer', [
        'prefixRule' => '   ',
        'suffixRule' => ' /* this is a real CSS rule */',
        'sepSelectors' => ', ',
        'sepRules' => '; ',
        'prefixOCB' => ' ',
        'suffixOCB' => "\n",
        'prefixCCB' => "\n",
        'suffixCCB' => ''
    ]);
    …
    $mysheet->init($settings);
    
    

    Приведу таблицу всех возможных префиксов и суффиксов:
    Название Значение по-умолчанию Описание
    prefixRule 4 пробела Строка, вставляемая перед каждым правилом
    suffixRule Пустая строка Строка, вставляемая после каждого правила
    sepSelectors , Разделитель между селекторами
    sepRules ;\n Разделитель между правилами
    prefixOCB Пробел Строка, вставляемая перед открывающейся фигурной скобкой (OCB – opening curly bracket)
    suffixOCB \n Строка, вставляемая после открывающейся фигурной скобки
    prefixСCB \n Строка, вставляемая перед закрывающейся фигурной скобкой (CCB – closing curly bracket)
    suffixCCB \n Строка, вставляемая после закрывающейся фигурной скобки
    prefixAtRuleLine 4 пробела Строка, вставляемая перед каждой строкой внутри @-правила
    suffixAtRuleLine Пустая строка Строка, вставляемая после каждой строки внутри @-правила

  • Другие возможности
    В библиотеке есть и другие возможности и функции, которые я просто перечислю списком. К ним относятся:
    — Включение и отключение компиляции правила (через символ ~, добавляемый перед правилом)
    — Автоматическое встраивание мелких изображений в код CSS с помощью data: URL
    — Импортирование других MSS и CSS файлов с помощью директивы @ import (над этой возможностью я еще работаю; в частности, нужно добавить возможность задания опций импортирования)
    — Преобразование всех цветов к одному формату
    — 2 поддерживаемых языка для текста ошибок компиляции: английский (en_us) и русский (ru_ru)
    — Включение и отключение расширений парсера библиотеки (можно отключить поддержку функций, переменных, цветов и т.п.)

Как это было…


С описанием того, чего я достиг на сегодняшний день, я вроде бы закончил. А теперь, я хочу рассказать, как я вел процесс разработки – как в перерывах между работой, учебой и моей любимой девчонкой, я писал свою библиотеку.


Рис. 1 – Моя любимая девчонка

С того самого момента, как я затеял свой мини-проектик, прошло уже немало времени. Возникало много разных сомнительных ситуаций и вопросов. И оно не мудрено – до этого я никогда ничего подобного не делал. Никакой специальной литературы по компиляторам я не читал и сначала делал все исключительно на свое усмотрение. Я не хвастаюсь, а даже наоборот говорю, что зря я этого не сделал. Возможно, так бы я избежал некоторых возникавших проблем.

Я почему-то сразу решил делать библиотеку в виде open-source проекта и сразу сказал себе, что одной гитхаб-странички будет недостаточно. После того как часть библиотеки была уже написана, я начал делать для нее сайт, чтобы в «режиме онлайн» находить дефекты и исправлять их, а также сразу на практике осознавать, что было бы приятно видеть конечному пользователю.

Название для своей библиотеки я придумывал, действуя от обратного. Я решил не отходить от примера самых известных на сегодняшний день препроцессоров SASS и LESS, и подумал, что MSS – неплохое сочетание букв, в конце концов. А чтобы название было запоминающимся, я решил назвать свой проект MySheet. И в аббревиатуру укладывается (MySheet Styles), и лёгкая изюминка в названии есть.

Название придумано, пора начинать проектировать корабль. Первое, что я начал делать – это был парсер исходного кода. Вот тут-то я и просчитался в первый раз. Я начал делать его без разбития исходного кода на токены и ключевые слова, и ориентировался на то, что в библиотеке будет фигурировать в-основном работа со строками. Конечно, минусы этого подхода я видел уже на этапе его выбора, но ничего лучшего, к сожалению, я придумать не смог. Уже потом, во время того как в университете у нас читался курс по компиляторам, я понял, что лучше было бы ввести этап предварительного лексического анализа. Хотя бы, потому что сейчас я столкнулся с проблемой распознавания и запоминания комментариев для последующего их вывода в скомпилированный код CSS. Или, например, теперь мне бы хотелось добиться нечувствительности расширений парсера (которые подключаются к библиотеки в виде дополнительных модулей) к наличию нежданных переносов строк и отступов (тех самых, которые часто добавляются для удобочитаемости кода). В ближайшем будущем, я хочу включить в парсер этап разбития на токены, что, по моему мнению, должно разрешить эти проблемы.

Идея с арифметикой цветов мне пришла в голову, когда я реализовывал поддержку математических выражений. Я подумал, что неплохо было бы иметь возможность осветления и затемнения цветов в дизайне сайта, чтобы подобрать сочетающийся цвет можно было без необходимости открытия color picker’а. Сейчас в библиотеке реализована работа с HSLA, RGBA, HEX и HTML форматами цветов. К каждому из цветов, заданных в данных форматах, можно добавить дельту любого канала из какого-либо другого формата цвета. Например, к цвету записанному как #000 можно добавить 255 пунктов синего канала и 40 пунктов зеленого, получив при этом цвет #0028ff. Арифметическое выражение будет выглядеть в данном случае следующим образом: #000 + 255b + 40g.

Реализовывая работу с цветами, я решил не изобретать свой велосипед и использовать уже существующую библиотеку MrColor (хотя без «допиливания» этой библиотеки не обошлось).

Кстати, в этот момент я задумался над оптимизацией библиотеки. В качестве профилировщика кода я использовал Webgrind. Не самый лучший профилировщик, но дело свое он делает, и он мне очень помог в обнаружении слабых мест в моем коде.

Хочу рассказать одну интересную штуку. Когда парсер в препроцессоре начинает парсить какое-либо правило, он проходит по всем зарегистрированным модулям и, грубо говоря, вызывает в каждом из них метод parse. А так как арифметическое выражение и, например, функция – две разные сущности, то библиотеке приходилось парсить одно и то же два раза. Мне это очень не нравилось, и в один прекрасный день я придумал решение. Когда парсер арифметического выражения обнаруживает, что перед ним все ж таки никакое не выражение, а простая функция, он не возвращает false, а возвращает этот самый объект функции. Тем самым я избавился от этого изъяна и увеличил производительность процесса парсинга исходного кода.

Еще хочу рассказать, как я делал свой собственный первый логотип. В поисках идеи для логотипа, я набрел на картинку с тюбиками краски, и подумал: «Тысяча чертей! Да это же просто замечательная идея!». Я посмотрел несколько уроков по рисованию в Фотошопе и, в итоге, у меня получилась вот такая клякса:


Рис. 2 – Моя клякса

Мои дальнейшие планы


В первую очередь, я хочу все ж таки добавить предварительное разбитие исходного кода на токены. А во вторую очередь, я хочу развивать свою библиотеку и дальше, выдирая хорошие фишки из других препроцессоров и перенося их в свой. Например, я хочу реализовать автоматическое добавление браузерных префиксов и возможность наследования правил.

Потом, я хочу пойти в сторону развития функционала по редактированию стилей и поиску блоков в коде MSS из бэкэнда. Например, можно добавить поддержку условных комментариев IE прямо в исходном файле (не знаю как вас, но меня всегда раздражало, что патчи для IE нужно сохранять и включать на страницу в виде отдельных файлов, тем более, если это всего полтора CSS-правила).

Если у кого-то есть какие-то ещё идеи по совершенствованию моего проекта, я всегда буду рад их услышать.

Вместо заключения


Ну вот, наверное, и всё, что я хотел бы рассказать в своей первой статье про мою библиотеку. Думаю, я смог немного прорекламировать мой продукт. Еще хотелось, так сказать, зафиксировать точку в разработке и получить отзывы о уже проделанной работе.

Буду благодарен вам за возможные советы и рекомендации, а также за всевозможную поддержку и просто теплые слова.

Если вам понравилась моя статья, то я обязательно буду писать ещё.

Ссылки


GitHub: http://ift.tt/1bCZEtx
Оффициальный сайт: http://mss.flydigo.com/
Документация: http://ift.tt/1ITTTlD

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] 3 вопроса по CSS, которые вызывают трудности

Думаете, вы знаете CSS? Шесть месяцев назад я предложил бесплатный тест для всех, кто думает также. В течение этого времени его прошли более чем 3000 людей. Средний бал составил 55%.

Конечно, среднее значение не так уж интересно. Более интересны те вопросы, на которых «засыпалась» большая часть посетителей. В этой статье я расскажу о трех вопросах, в которых ошибались чаще всего.

Вопрос 1: Как лучше установить line-height?

Этот вопрос должен быть простым для тех, кто постоянно работает с текстами.

Если вы хотите установить двойной интервал на всем вашем сайте, какое из следующих значений line-height лучше всего подойдет для этой цели?
— 200%;
— 2em;
— 2;
— double;

Только 31% ответили правильно на этот вопрос. Остановитесь на минутку и выберите ответ для себя.

Во-первых, double – это отвлекающий маневр. Я счастлив сказать, что только 9% людей выбрали его. Остальные три ответа были довольно популярны.

Ответ, который выбрало большинство людей — 2em (39%). Действительно, 2em, несомненно, даст вам двойной интервал для элемента, в котором он применен, но тоже делает и значение 200%, но только 21% понравился этот ответ!

Однако правильным ответом является значение 2 (вариант №3).

Это урок, который я усвоил очень давно, когда только начинал изучать CSS. Всегда указывайте line-height, как безразмерное число; таким образом, дочерние элементы, которые используют различные font-size, будут наследовать это число, а не фиксированную высоту строки.

Скажем страница имеет размер шрифта в 12pt, но она также содержит заголовок с размером шрифта в 24pt. Если вы установите line-height: 2em; (или 200% ), то вы получите высоту строки 24 пт (в два раза выше размера по умолчанию) во всем документе. Поэтому заголовок будет отображаться с одним интервалом.

line-height: 2; указывает браузеру, что он должен сохранять соотношение размера шрифта и высоты строки 1:2, даже если размер шрифта меняется.

Вопрос 2: Перекрытие элементов


Этот вопрос был немного сложнее. Он требует некоторого опыта в использовании «грязных трюков».

Какое из следующих свойств CSS, использованное само по себе, может вызвать перекрытие элементов?
— z-index;
— margin;
— overflow;
— background;

Выбрали ответ?

Естественно, мы сразу можем отмести background. Только 2% участников тестирования указали его как правильный ответ.

К сожалению, большинство опрошенных выбрало z-index. Ему отдали предпочтение 46% людей. Возможно, они неверно восприняли вопрос или просто не понимают, как работает z-index. Само по себе это свойство не имеет никакого эффекта, кроме него вы должны будете указать это свойство и для другого элемента, а также установить позицию для каждого из них. Короче говоря, Z-индекс позволяет контролировать порядок элементов, которые перекрываются, но, в первую очередь, они должны накладываться друг на друга.

Overflow довольно просто исключается из списка вариантов, если вы хотя бы пару раз использовали это свойство. Оно определяет поведение содержимого, которое не помещается внутри контейнера. Поведение элемента зависит от размера контейнера и значений других свойств. Опять же, само по себе, это свойство не приведет к перекрытию. Но, так или иначе, его выбрали 22%.

В итоге, у нас остается один ответ, а именно «margin». Вам может показаться странным, что свойство, которое управляет отступами от края элемента, может привести к перекрытию. Ответ прост: для этого необходимо использовать отрицательные значения.

Отрицательные поля являются чрезвычайно полезными для размещения HTML элементов.

img

Вопрос 3: Псевдо-элементы против псевдо-классов


Последний вопрос немного каверзен, я признаю.

Какие из следующих эффектов лучше всего делать с помощью псевдо-элементов?
— Добавить к гиперссылке тень, которая появляется при наведении.
— Изменить цвет элемента checkbox при его выборе.
— Окрасить четные и нечетные строки таблицы различным цветом.
— Всегда отображать жирным шрифтом первую строку абзаца на адаптивной странице.

Три из этих эффектов достигаются при использовании псевдо-класса; только один требует использовать псевдо-элемент. Можете ли вы сказать, какой именно?

Псевдо-класс — определенное состояние, в котором может оказаться элемент. Думайте об этом как о классе, который применяется к элементу автоматически при определенных условиях.

Псевдо-элемент — это часть документа, для которой CSS позволяет использовать стили, даже несмотря на то, что он фактически не является HTML-элементом. Это что-то вроде виртуального HTML-элемента ¬– то, для чего вы можете создавать стили, хотя он, и не заключен в HTML-теги.

Учитывая это различие, давайте попробуем отыскать правильный ответ:

Добавить к гиперссылке тень, которая появляется при наведении

Гиперссылка — существующий HTML элемент. Применение стилей к нему только в конкретной ситуации (когда указатель находится над ним) означает, что мы используем псевдо-класс. Псевдо-класс, который вы бы могли использовать в этом случае: :hover.

22% участников тестирования думали, что это псевдо-элемент.

Изменить цвет элемента checkbox при его выборе

Опять же, checkbox является существующим HTML элементом, не виртуальным. Когда флажок установлен, браузер применяет к нему псевдо-класс :checked.

20% участников тестирования думали, что это псевдо-элемент.

Окрасить четные и нечетные строки таблицы различным цветом

Это то, чем мы действительно обманули людей, но мы все еще говорим о применении стилей к реальным элементам HTML. TR, как четные, так и нечетные, существуют, и мы просто применяем к ним стили, в зависимости от их состояния (четная/нечетная).

Используйте :nth-child(even) (или :nth-child(2n)) для четных и :nth-child(odd) (или :nth-child(2n+1)) для нечетных.

Я предполагаю, что 36% участников выбрали этот вариант только потому, что: nth-child и псевдо-элементы — довольно непонятные особенности CSS.

Всегда отображать жирным шрифтом первую строку абзаца на адаптивной странице

Давайте вспомним разницу между псевдо-классами и псевдо-элементами.

При адаптивной верстке вы не можете четко сказать, что вот этот элемент содержит первую строку. Ее содержание будет меняться в зависимости от ширины окна просмотра.

:first-line — псевдо-элемент, который позволяет применять стили к первой строке текста в блоке, независимо от того, где она заканчивается.

Полезные решения Paysto для читателей Хабра:

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 24 апреля 2015 г.

Разворачиваем СвоёОблако в облаке. Установка CoreOS

Меня всегда интересовали облачные технологии. В том числе и наиболее трендовые из них — это децентрализация, кластеризация, оптимизация и распределенние всего: вычислительных ресурсов, данных, пончиков и власти. Поэтому я не мог пройти мимо CoreOS, о которой в IT-сообществе сейчас много разговоров, и которая стала для меня отправной точкой для экспериментов.

Чтобы совместить приятное с полезным, я стал искать подходящее приложение, на котором, с одной стороны, было бы интересно применить облачные технологии, а с другой, — могло бы пригодиться в будущем. Поэтому, я решил развернуть инсталляцию OwnCloud на базе CoreOS.
Теперь я расскажу, к чему это привело, и по ходу действия приведу ссылки, чтобы интересующийся мог углубить свои знания в предметной сфере. Но если возникнут вопросы — смело задавайте их в комментариях.

Итак, я поставил себе задачи:

  1. Установить CoreOS на bare-metal сервер
  2. Настроить распределенное хранилище данных
  3. Написать свой Dockerfile и запустить приложение в кластере
  4. Настрить автоматическое обновление и регистрацию контейнеров
  5. Рассмотреть неиспользованные технологии и придумать им применение(*)

В этой статье я расскажажу об установке CoreOS. О настройке и дальнейших экспериментах — в дальнейших.

Установливаем CoreOS на bare-metal сервер


Чтобы установить OwnCloud на сервер, нужно обзавестись сервером.
Инсталляция на виртуальные сервера «за деньги» или такие же виртуальные сервера на локальной машине не столь интересна, как установка на «живое» железо. Но с текущем курсом доллара арендовать сервера — дорогое удовольствие. Поэтому был совершен рейд на гугл, чтобы найти провайдера, предлагающего дешевое и «металлическое» решение.

Гугл предложил мне две компании, обе французские: Kimsufi и Online.
Kimsufi — дочка OVH, одного из крупнейшего хостера.
Online — дочка iliad, одной из крупнейших телекоммуникационных компаний.

Обе компании предлагают дешевые и при этом мощные решения. Хоть по отзывам у online.net сеть лучше, мой выбор пал на Kimsufi по двум субъективным причинам: 1) сервер на VIA® Nano® U2250 слишком медленный, а за следующий в линейке просят уже 16 евро — жаба душит; 2) наличие верифицированного аккаунта у OVH/Kimsufi.

Регистрация


О регистрации, подтверждении и снятии VAT у провайдера Kimsufi было много сказано (хабр). Единственное, о чем стоит предупредить — это о времени ожидания. Создается впечатление, что поддержка у Kimsufi работает по остаточному принципу — проблемы кастомеров решаются только когда от «большого брата» (OVH) нет задач. Стоит это иметь в виду, если хотите размещать там production.

Покупаем сервера


Я заказал три сервера. Почему три? Потому что только три сервера могут гарантировать отказоустойчивость и отсутствие split-brain.
Краткое объяснение
В тестируемых дальше продуктах для расчета fault tolerance используется формула (n-1)/2, из которой видно, что минимальное значение числа n равно трем. В нашем случае, пруфы можно найти в доке etcd и Percona XtraDB Cluster, обсудить — в комментарии.

Так как желающих приобрести сервера за такую цену очень много, стать счастливым обладателем своей железки «честным» путем довольно сложно. Лично я для их поимки воспользовался этим скриптом.
Различие серверов линейки KS-2

KS-2a — проц N2800, диск 2ТБ HDD(или я везунчик ^_^)
KS-2b — проц D425, диск 1ТБ HDD
KS-2c — проц N2800, диск 40ГБ SSD

Установка CoreOS


Как только сервера оплачены и прошли предпроверку, вы получите сертификат сервера по почте. Сразу же после этого сервера будут доступны через веб-админку.
Как избавиться от назойливого попапа в админке и нюансы refund политики
После появления сервера в админке Kimsufi, нам будут предлагать установить ОС до тех пор, пока мы не установим хоть что-нибудь через веб-интерфейс. После этого вы не сможете вернуть свои деньги за сервер.

Владельцы ДЦ могут оценить некоторые фичи

Для начала нам нужно попасть в рекавери, а дальше установка идет по официальной инструкции установки на диск.
Чтобы загрузиться в Rescue, в веб-интерфейсе кликаем на Netboot -> Rescue. После этого сервер нужно перезагрузить, проще всего это сделать жмякнув на кнопку Restart. Пароль для входа придет на почту.
Как только авторизовались на сервер по SSH, загружаем скрипт установки
wget http://ift.tt/1wpQZ3l
chmod +x coreos-install
./coreos-install --help


Пишем свой cloud-init файл и процесс установки через coreos-install -C stable -c /path/to/cloud-init -d /dev/sda.

После того, как установка завершится, можно внести изменения вручную: добавить ssh-ключ или отредактировать cloud-init. Для этого необходимо примонтировать ROOT партицию — она под номером девять. Например:

mount /dev/sda9 /mnt
echo 'ssh-rsa AAAAB... user@domain' > /mnt/home/core/.ssh/authorized_keys


Либо же можно положить ключ через cloud-init:
cloud-init номер один
#cloud-config
hostname: core1

coreos:
write_files:
  - path: /home/core/.ssh/authorized_keys
    permissions: 0600
    owner: core
    content: |
      ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+jxun+xn31x4tP7NdM6nMFI5b00bbk+VK4JM5mdyS+30/lIhhArMWnhla7NTw0BINdvutErZRFzhIqf5yaR/+O7/Oqc9J53dWJiEnz0si9hutbVSYA/Peo0Z9nFBm6Aep3816AzJYNzKIZg17JwqTKpEnV/ArXOmbCek9hi50R7yuZvtehWmJMNqTxKhqb5aD1joARd2iTMfS39pFsLsrxn8b2mGfcQH9v0+HwmNEiCGpq+HCMFTpt9Z1SOukeTpKOWOiBEzQPqaeaIeqXTDHHj2zWHv0/elIuRBFpxgC00DvoshlAzmB6CwCttBkigGQP2Mlcnovuo0RyuJRAlw1 user@domain




cloud-init номер два
#cloud-config
hostname: core1

coreos:
write_files:
  - path: /etc/ssh/sshd_config
    owner: root
    content: |
      # Use most defaults for sshd configuration.
      UsePrivilegeSeparation sandbox
      Subsystem sftp internal-sftp
      ClientAliveInterval 180
      UseDNS no
      AuthorizedKeysFile  %h/.ssh/authorized_keys.d/coreos-cloudinit

ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+jxun+xn31x4tP7NdM6nMFI5b00bbk+VK4JM5mdyS+30/lIhhArMWnhla7NTw0BINdvutErZRFzhIqf5yaR/+O7/Oqc9J53dWJiEnz0si9hutbVSYA/Peo0Z9nFBm6Aep3816AzJYNzKIZg17JwqTKpEnV/ArXOmbCek9hi50R7yuZvtehWmJMNqTxKhqb5aD1joARd2iTMfS39pFsLsrxn8b2mGfcQH9v0+HwmNEiCGpq+HCMFTpt9Z1SOukeTpKOWOiBEzQPqaeaIeqXTDHHj2zWHv0/elIuRBFpxgC00DvoshlAzmB6CwCttBkigGQP2Mlcnovuo0RyuJRAlw1 user@domain



Во время первой загрузки системы запускаются скрипты, которые делают некоторую магию (чинят GPT, делают resize корневой файловой системы(/dev/sda9) и пр.

Примечание
Подробнее о партиционировании CoreOS в доке, в mail list'ах или на гитхабе.

Чтобы загрузиться в свежеустановленную ОС, нужно сменить порядок загрузки через веб-интерфейс -> Netboot на загрузку с жесткого диска и отправлить сервер в перезагрузку(командой reboot в терминале, либо кнопкой Restart в веб-админке).
Если вы не забыли положить ssh-ключ или в cloud-init'е указали своего пользователя, то вас должно пустить в систему. Если удалось — поздравляю! Если нет — что-то пошло не так.

Переразбивка диска


Как только система установлена, можно приступить к её изучению. А интересного много: etcd, fleet, systemd и связанные с этим технологии: kubernetes, confd и многое другое!
Но перед тем, как идти дальше, я решил создать две дополнительных партиции: для хранения пользовательских данных (распределенная) и для хранения контейнеров и системных приложений (btrfs).
Почему была выбрана btrfs, если она является экспериментальной? Потому что цель моего эксперимента в том, чтобы поэксперементировать с новыми технологиями. И несмотря на то, что btrfs была вот уже пару лет стабильно работает на десктопах/ноутбуках, в продакшне я её не использовал.
Историческая справка

Изначально под корень в CoreOS создавалась btrfs партиция. С недавних пор для корня используется ext4 с AUFS/OverlayFS. Причина ухода с btrfs связана с двумя неприятными багами, которые должны были исправить с версии ядра 3.18, в чем клянутся разработчики. Тем не менее, btrfs возможно до сих пор имеет некоторые проблемы при работе со большим кол-вом (несколько тысяч) слоёв (снэпшотов), но обсуждение этого выходит за рамки данной статьи. Пишите комментарии!


Для того, чтобы что-то выделить, нужно в начале освободить! Для этого нужно вернуться обратно в Rescue.
Если вы не можете загрузиться в Rescue

Я столкнулся с проблемой: после свежей установки CoreOS, машинка не захотела загружаться по PXE, в том числе и в rescue. Если и у вас беда приключилась, можно воспользоваться одноразовым хаком: через iptables блочим весь ICMP-трафик (systemctl show iptables-restore.service) и делаем Restart через веб-интерфейс. Автоматика посчитает, что сервер не загрузился, и инженер вручную загрузит его в Rescue. Правильно починить это можно только сменой материнской платы и перебивкой мак-адреса на свитче силами инженера.


Я уменьшил размер ФС, затем уменьшил размер партиции и уже после создал новые.
Корень находится на девятой партиции: /dev/sda9. Приступим:
e2fsck -yf /dev/sda9 # проверяем ФС на наличие ошибок
resize2fs /dev/sda9 100G # изменяем размер корневой ФС до 100ГБ
gdisk /dev/sda # меняем размер партиции
resize2fs /dev/sda9 100G # чтобы убедиться, что всё ок


В gdisk'е нужно удалить партицию и создать новую с измененным размером.
Если мне не изменяет память, то сочетания клавиш будут сделующие: d -> 9 -> n -> 9 -> -> +100G -> -> c -> 9 -> ROOT -> w -> Y ->
Если вы сделали всё верно, то можете спокойно загрузиться с жесткого диска и увидеть, что /dev/sda9 занимает 100ГБ или 93ГиБ.
core3 ~ # df -h /dev/sda9
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda9        97G  128M   93G   1% /


Несмотря на то, что я устанавливаю CoreOS на сервера Kimsufi, инструкция подходит и другим провайдерам. Если вы столкнулись с нюансами при установки — пишите, обсудим.

На этом рассказ об установке CoreOS на bare-metal сервер Kimsufi завершен.

Who's next?


В следующей статье я расскажу, как создать партиции из освободившегося места, как настроить RTM (Real Time Monitoring — скрипт мониторинга для отрисовки красивых графиков в веб-админке OVH), etcd, fleet и как развернуть распределенную ФС.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Семь принципов эффективности дата-центра от Microsoft


Вид с воздуха на дата-центр Microsoft в Сан Антонио, Техас (площадь ДЦ 43660 м2)

Пол Слейтер — один из наиболее вовлеченных в телекоммуникационную сферу работников Microsoft. Он — один из сотрудников корпорации, которые отвечают за стратегию развития корпоративных дата-центров. При этом Пол считает, что автоматизация дата-центров, с максимальным замещением роботами работников-людей — дело следующих 10 лет.

Кроме того, Пол Слейтер видит и некоторые другие направления развития дата-центров, которые он недавно изложил слушателям на конференции Data Center World, которая проходила в Лас-Вегасе. Эти направления, или принципы эффективности дата-центров изложены в продолжении.

Стоит отметить, что у Microsoft — весьма неплохие дата-центры, которые многие специалисты называют наиболее эффективными дата-центрами в мире.

1. Проектирование дата-центра в соответствии с местными условиями

Ответ на вопрос «что делает дата-центр эффективным» достаточно сложный, комплексный. Это — одна из причин того, что поставщики решений для дата-центров предлагают разное оборудование, которое, зачастую, значительно отличается от аналогов по тому либо иному принципу.

По мнению Microsoft, одним из основных факторов, которые стоит принимать во внимание при создании дата-центра это местные условия. К примеру, дата-центр может быть построен в месте, где земля и электричество очень дешевы. В этом случае можно без особых затрат строить крупные дата-центры с высоким уровнем энергопотребления. Если же компании нужен дата-центр посреди Манхэттена, это совсем другая история, здесь имеет значение каждый квадратный метр и ватт энергии.

Все это может казаться очевидным, но многие компании строят дата-центры по типовым решениям, вовсе не учитывая местных условий. В таком случае высокой отдачи при низких операционных затратах ждать не стоит.

2. Стандартизация и унификация

Наиболее эффективным будет дата-центр, пространство которого используется на все 100%. Корпорация старается заполнять пространство дата-центров, принадлежащих ей, полностью, в максимально сжатые сроки. Кроме того, оборудование, которое использует компания, стандартизированное.

В этом случае операционные затраты на обслуживание оборудования (которое может внезапно выйти из строя) ниже, чем в случае использования какого-либо особенного оборудования и элементов.

Управление своими дата-центрами компания старается вести при помощи таких инструментов, как, например, Data Center Infrastructure Management.

3. Проектируем возможность изменений

С течением времени дата-центр и требования, предъявляемые к нему, будут меняться. Поэтому при создании дата-центра стоит заранее заложить возможность изменений. Ведь технологии изменяются настолько быстро, что мало кто успевает к ним сразу приспособиться. Технологические решения, применяемые сегодня, возможно, придется менять уже через полгода. При этом сейчас можно даже не представлять себе, что именно подвергнется изменениям.

В качестве примера можно привести замену обычных дисков на SSD — это привело к некоторым изменениям в термодинамике дата-центров. Предположить сразу это было весьма сложно, если вообще возможно.

4. Автоматизация

Автоматизация процессов наиболее эффективна в случае гомогенной, однородной среды. Если все унифицировано, как и рекомендовалось выше, то ввести процессы автоматизации будет простым заданием (все относительно, конечно).

Два года назад Microsoft постаралась стандартизировать все свое оборудование и процессы в дата-центрах. Как результат — позже удалось быстро развернуть некоторые решения по автоматизации ряда процессов. При этом Microsoft старается активно участвовать в проекте Open Compute Project, используя свои наработки и наработки партнеров.

Открытость — еще один важный фактор.

5. Интегрированная среда

Если вы хотите получить эффективный дата-центр, считает Слейтер, стоит планировать сверху вниз. Вначале нужно рассмотреть возможности процессов и приложений, которые должно поддерживать оборудование дата-центров, а после уже подбирать само соборудование.

Если все пошло правильно, проектирование завершится созданием высокоинтегрированной системы, где каждый элемент используется в работе, поддерживая то либо иное приложение или сервис.

6. Надежное, гибкое ПО

Хорошее, надежное программное обеспечение окупает себя достаточно быстро. При этом при помощи ПО можно «выжать» из оборудования максимально возможную эффективность работы, даже, если оборудование несколько морально устарело.

В корпорации Microsoft используют облачные решения, и своим клиентам компания также стала предлагать облачные решения вместо отдельно взятых лицензий на ПО. Поэтому сейчас компания может масштабировать свою работу, в очень значительной степени.

Одним из наиболее ярких примеров внедрения облачного ПО является сервис Microsoft Office 365.

7. Дата-центр должен быстро реагировать на изменения

Чем быстрее будет введен в работу новый сервис, тем лучше. При этом сервис должен поддерживаться оборудованием самого дата-центра. Соответственно, изначально проект дата-центра должен включать возможность подобных изменений и поддержки нового оборудования. Таким образом, считает Слейтер, можно «оседлать закон Мура».

При этом он подчеркивает необходимость описанной выше стандартизации оборудования и сервисов. Это позволяет наиболее эффективно использовать ресурсы своего ДЦ.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Обзор дополнений для Blender 3D


Не секрет, что пакет Blender 3D набирает всё большую популярность среди 3D дизайнеров и Indie разработчиков игр, т.к. является очень мощным и бесплатным средством разработки. Обладая возможностью не только моделирования, но и скульптинга, рисования текстур по модели, подготовки модели для 3D печати, motion catpure и еще многими и многими возможностями. Однако, разработчики Blender`а часто акцентируют внимание на то, что много полезных функций реализованы в виде addon`ов, которые включены в дистрибутив, но не включены для работы по умолчанию. Моделирование, это моё хобби и под катом, моя подборка дополнений, которые на мой взгляд сильно облегчают жизнь и упрощают моделирование. В данной статье, я рассмотрю дополнения, которые работают с актуальным на данный момент Blender 2.74.

Данное дополнение позволяет всего несколькими кликами, вставить в сцену плоскость с натянутой на нее картинкой. Работает как для Blender Internal, так и для Cycles, самостоятельно создавая необходимые узлы и материалы. Поддерживаемые форматы: jpeg, jpg, png, tga, tiff, tif, exr, hdr, avi, mov, mp4, ogg, bmp, cin, dpx, psd. Входит в стандартный комплект Blender 2.74.

Подключается здесь: Community->Import-Export->Import Images as Planes.
Активируется через: Shift+A->Mesh->Images as Planes.
Пример:

Документация


Позволяет гибко работать с узлами в Cycles и ускоряет процесс создания материалов. Например, при стандартном создании материала вам необходимо смешать два шейдера. Для этого, вы ищете и вставляете Mix Shader, далее подключаете его к Material Output и самим шейдерам. С помощью Node Wrangler, достаточно всего лишь одного движения мышки, что бы сделать то-же самое.
Пример (Нажимаем Alt+ПКМ):

Подключается здесь: Community->Node->Node Wrangler
Входит в стандартный комплект Blender 2.74. У данного аддона еще много горячих клавиш и возможностей. Подробнее в документации.
Это дополнение автоматически рассчитывает оптимальный размер Tiles при рендере в Cycles в зависимости от используемого устройства для расчета. Время получения финального изображения уменьшается. Входит в стандартный комплект Blender 2.74.
Подключается здесь: Community->Render->Auto Tile Size

Документация
Скрипт сканирует папки с blend файлами, и отображает в виде списка объекты, текстуры, материалы и т.д. которые могут быть добавлены в вашу сцену.

Скачать
Документация
Добавляет в Blender панельку для экспорта модели в Unreal Engine 4. Позволяет настроить параметры сцены в UE, переименовывать объекты, настраивать pivot и экспортировать анимацию. Не входит в стандартный набор Blender. Автором является не программист, а 3d artist, который просто хочет облегчить себе работу и, по моему, у него не плохо получается.
Пример работы:

Сайт разработчика

Прошу вас в комментариях поделиться дополнениями которыми пользуетесь вы. Буду рад, любым отзывам.

Update. Описание дополнений из комментариев:


Позволяет быстро создавать реалистичные деревья.
Подключается здесь: Community -> Add Curve -> Sapling
Активируется: Shift+A->Curve->Add Tree
Обучение по работе с данным дополнением:

Документация
Спасибо gatools

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Intel Edison. Первый запуск

Intel Edison

В прошлом году на выставке CES 2014 Intel представила Linux мини-компьютер Edison для интернета вещей. Кратко почитать об анонсе Intel Edison можно тут. Интернет вещей — безусловный тренд этого года. Теперь не только домашний компьютер, планшет, или смартфон могут подключаться к сети Интернет. Но и привычные бытовые вещи, которыми мы пользуемся каждый день. Intel Edison, не ответ компании на Raspberry Pi, а новая ниша миниатюрных энергоэффективных компьютеров, в форм-факторе модуля. Любой разработчик может взять такой модуль, добавить к нему различные датчики, механику, и средство интерактивного взаимодействия, и получить новое устройство.

Intel Edison – мини-компьютер с Linux на борту, базис для построения нового мира вещей основанного на сетевом взаимодействии друг с другом.

Intel Edison

Спецификация модуля Intel Edison

  • Процессор: SoC «Tangier», включает в себя два ядра Intel Atom (Silvermont) processor (22nm) processor @ 500 MHz и 32-bit Intel Quark micro-controller @ 100 MHz
  • Оперативная память: 1 GB LPDDR3 (PoP memory) – 2 channel 32bits @ 800MT/sec
  • Пользовательская память: 4 GB eMMC (v4.51 spec) +разъем для micro SD карт
  • Сеть: 802.11 a/b/g/n Wi-Fi (с поддержкой 5 ГГц, чип Broadcom 43340) с встроенной антенной или внешней, и Bluetooth 4.0
  • USB: один разъем micro USB
  • Программируемые контакты:
    • 2x UART (1 full flow control, 1 Rx/Tx)
    • 2x I2C, 1x SPI with 2 chip selects
    • 1x I2S
    • 12x GPIO включая 4 контакта ШИМ(PWM)

  • Подключение периферийных модулей: 70-контактный разъем (Hirose DF40 series – 1.5, 2.0, или 3.0 mm высоты стека)
  • Питание: вход от 3.3 до 4.5 V; выход: 100mA @ 3.3V и 100 mA @ 1.8V
  • Режимы питания: ожидание( без радиопередачи): 13 mW; ожидание (Bluetooth 4.0): 21.5 mW (BLE in Q4 2014); ожидание (Wi-Fi): 35 mW.
  • Размеры: 35.5 × 25.0 × 3.3 mm
  • Температурный диапазон работы: от 0 до 40°C

Для удобной разработки своих устройств на базе Edison, Intel подготовила плату Intel Edison Board для Arduino. Ссылки на магазины для приобретения комплекта Intel Edison with Arduino Breakout Kit, в конце поста.

Intel Edison Board для Arduino
Intel Edison

Содержит:

  • 20 цифровых input/output контактов, включая 4 контакта вывода ШИМ(PWM)
  • 6 аналоговых контактов input
  • 1 UART(RX/TX)
  • 1 I2C
  • 1 ICSP 6-pin header (SPI)
  • Micro USB разъем или (используя переключатель на плате) выделенный стандартный USB host Type-A
  • Разъем Micro USB (для UART)
  • Слот для SD Card памяти
  • Стандартный разъем для питания DC power jack (7V – 15V DC input)

Программное обеспечение
Intel Edison включает в себя Intel Atom, который работает под версией Linux Yocto 1.6, и Quark MCU под ViperOS(усеченный вариант VxWorks от Wind River). В инструменты разработчика входит Arduino IDE, Eclipse с поддержкой C, C++, поддержка Python и Intel XDK для Node.JS и HTML5 разработчиков. Достаточной богатый набор средств разработки.

Общая схема поддержки средств разработки

Intel Edison
Постер демонстрирующий программный стек Intel Edison

Intel Edison

Загрузка ОС: U-Boot version (2nd stage bootloader in source)
Kernel/BSP: Yocto Linux 1.6, Linux kernel v3.10.17
Native SDK: компиляция (GCC 4.8.2), GLIB 2.38.2, отладка GDB 7.6.2
Инструменты разработчика: Arduino IDE, Node.js (Supported by Intel® XDK), Python (This package is part of BSP)
Интерфейсы WLAN/BT (BCM43340): в виде бинарных файлов WiFi STA and BT+LE, доступен исходный код для BRCM kernel drivers и WiFi Supplicant(+BlueZ)
Межплатформенное взаимодействие: D2D и D2C (Networking, Messaging, privacy/security)
Облако: Веб-портал, управление идентификацией, профиль пользователя, регистрация устройства, загрузка данных с устройства и визуализация.

Yocto Project для Intel Edison
The Yocto Project – это Open Source-проект, целью которого является упрощение разработки специальных дистрибутивов Linux для встраиваемых систем и обеспечение их переносимости между различными платформами.

Руководство Intel Edison Board Support Package предназначено для инженеров, которые собирают свои образы (images), ядра, разрабатывают низкоуровневый код.

Предварительно скомпилированные BSP образы доступны на сайте Intel. BSP для Intel Edison поддерживает следующие возможности:

  • Ядро образа основано на Linux kernel 3.10.17
  • Поддержка U-boot, как второй метод загрузки ОС
  • U-boot second stage bootloader
  • Связь Bluetooth и Wi-Fi
  • Работа с Intel Cloud
  • Пакеты доступные в рамках Yocto project

Используемая в Intel Edison, стандартная ОС Linux основана на Yocto.

Сборка образа
Intel Edison

Платформа Intel IoT Analytics Platform (Beta)
Для работы с устройствами Edison и Galileo, Intel представила облачный сервис, работающий по принципу Machine-to-Cloud. Для передачи данных используются протоколы: HTTP и MQTT. Облачный сервис предоставляет:

  • Передачу данных на удаленные сервера с устройства
  • Хранение данных
  • Обработку данных по своим алгоритмам
  • Формирование событий, и оповещений

Intel Edison

Платы расширения для Intel Edison
По спецификациям Edison, компания DFRobot.com для Intel Edison разработала специальную робо-платформу Romeo for Edison Controller (With Intel Edison). Платформа предназначена для построения робота, на плате уже интегрированы драйверы двигателей, сервоприводы.

Intel Edison

Программирование в среде Arduino. Первый старт
Наиболее быстрым ознакомлением с Edison, будет работа с модулем как с Arduino совместимой платой. Разработка кода осуществляется в уже привычной родной IDE среде для Arduino. Но в базовой среде, загруженной с сайта arduino.cc не будет модуля работы с Intel Edison. Поэтому драйвера и среду необходимо загрузить с сайта Intel.

  • Windows Driver
  • Arduino Software 1.5.3 — Intel 1.0.4 software

Подключаем Intel Edison Board используя разъем J16 к ПК, и устанавливаем драйвера. После установки драйверов должно появиться вот такое окно:

Intel Edison

Запускаем IDE Arduino. После запуска необходимо выбрать плату. Меню Сервис-> Плата –> Intel Edison

Intel Edison

Демонстрация работы
Для работы был собран небольшой демонстрационный стенд. Для демонстрации работы Intel Edison как Arduino, используется:


Среда для датчика влажности почвы, цветочный горшок с Каланхое каландива.

Требуется периодически замерять уровень влажности почвы, температуру окружающей среды, давление и выводить значения на дисплей.
Для более удобного подключения кнопок, LCD, реле, LED, используется IO Expansion Shield for Arduino.

Схема подключения
Intel Edison

Датчик влажности почвы
Intel Edison

Демонстрационный стенд с цветком

Intel Edison

Код arduino скетча
Код arduino скетча
//LCD 
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define LCD_I2C_ADDR    0x20 // Define I2C Address where the PCF8574T is
#define BACKLIGHT     7
#define LCD_EN  4
#define LCD_RW  5
#define LCD_RS  6
#define LCD_D4  0
#define LCD_D5  1
#define LCD_D6  2
#define LCD_D7  3
LiquidCrystal_I2C       lcd(LCD_I2C_ADDR,LCD_EN,LCD_RW,LCD_RS,LCD_D4,LCD_D5,LCD_D6,LCD_D7);
//BMP085 Barometric Pressure & Temp Sensor
#include <Wire.h>
#include <Adafruit_BMP085.h>
Adafruit_BMP085 bmp;
//

void setup() {  
  bmp.begin();
  //init LCD
  lcd.begin (20,4);
  lcd.setBacklightPin(BACKLIGHT,NEGATIVE); // init the backlight
  lcd.setBacklight(HIGH); // Backlight on
  lcd.home ();                  // go home
  lcd.setCursor ( 0, 0 );        
  lcd.print("Edison. Habrahabr");
}

void loop() {
  lcd.setCursor ( 0, 1 );  
  lcd.print("Tempera. = ");
  lcd.print(bmp.readTemperature());
  lcd.print(" *C");
  //
  lcd.setCursor ( 0, 2 );        
  lcd.print("Pressure = ");
  lcd.print(bmp.readPressure());
  lcd.print(" Pa");
  //
  lcd.setCursor ( 0, 3 );        
  lcd.print("Moisture Value = ");
  lcd.print(analogRead(0));
  //
  delay(1500);               // wait for a second
}


Видео

Особенности
Несмотря на Arduino-совместимость программного обеспечения Intel Edison, не всегда код на Arduino напрямую без изменений можно перенести. С элементарными функциями не возникают проблемы, например отправка логической “1” или “0”, PWM, аналоговое чтение или запись, работа с Serial LCD. Код для модулей работающие на интерфейсах I2C и SPI, приходится править. Для запуска LCD и датчика давления пришлось немного “поколдовать”

Инструкция как запустить дисплей GY-IICLCD GY-LCD-V1 с чипом PCF8574T на I2C

1) По пути arduino-1.5.3-Intel.1.0.4\libraries удалить папку LiquidCrystal, далее будет заменена
2) Для работы дисплея требуется библиотека  F Malpartida’s NewLiquidCrystal library.
3) В архиве папку LiquidCrystal необходимо скопировать в arduino-1.5.3-Intel.1.0.4\libraries
4) Для справки Communities.intel.com. How to interface a i2c LCD to Galileo i2c port. В папке LiquidCrystal, файл FastIO.cpp. найти строку #include «FastIO.h» и ниже ее добавить #define _BV(i) (1<<i).
5) В этой папке LiquidCrystal, удалить файл LiquidCrystal.cpp, иначе будут ссыпаться ошибки
6) Дисплей теперь будет работать

Инструкция как запустить датчик BMP085 на I2C

1) Для справки Communities.intel.com. BMP085 Library Error
2) Выполнить инструкции рекомендованные в ветке форумы или сразу загрузить модифицированную библиотеку gustafn/Adafruit-BMP085-Library · GitHub
3) Обязательно переименовать папку библиотеки Adafruit-BMP085-Library, на что ни будь более короткое, пример AdaBMP085, иначе IDE Arduino будет ругаться что проблемы с ASCII названием.
4) Датчик теперь будет работать
Для решения проблем с подключением датчиком в первую очередь необходимо смотреть на communities.intel.com.
Как и ожидалось подключение стандартного двухстрочного дисплея HD44780 на I2C с PCF8574, используя стандартную библиотеку, не увенчалось успехом.
Библиотеки для подключения цветного LCD TFT 1.8", такого как в посте http://ift.tt/1GppDyT, Adafruit_ST7735 library и Adafruit GFX Library, не заработали.

Подведем краткий итог. Некоторая несовместимость с Arduino кодом объясняется достаточно недавним появлением Intel Edison, и разработчики еще не успели полностью закончить работу на библиотеками. Проект Arduino, в этом году отметит свой юбилей, прошло 10 лет с момента создания первой Arduino. Поддержка Arduino скорее продиктована, более знакомым и удобным средством разработки для большинства гиков. Сильной стороной Intel Edison, помимо богатых технических характеристик, является проработанная программная поддержка. Каждый разработчик используя бесплатные инструменты может собрать свой образ BSP Linux, на базе Yocto Project. Учитывая высокую производительность модуля и большое количество уже разработанных проектов под Linux, разработчику открываются широкие возможности по применению данного модуля. На данный момент обеспечено выполнения кода на C++, Python, Node JS. Загружать код под Node JS можно удалено, в отличие от Arduino, что очень удобно для разработчика.

Следующая статья будет посвящена взаимодействию Intel Edison с облачным сервисом Intel IoT Analytics Cloud. Какие возможности облако предоставляет разработчикам и конечны пользователям.

Пока шла подготовка статьи, был обнаружен интересный проект. У гикой существует достаточно специфическое соревнование, заключающее в запуске игры Doom, на самых разнообразных устройствах, включая принтеры. Так в конце прошлого года Хакеры сыграли в Doom на принтере. Теперь очередь пришла Intel Edison, проект Edidoom, с поддержкой геймпада по bluetooth.

Ссылки


Дополнение. Техническая спецификация Intel Edison Board для Arduino (перевод из Intel Edison Kit for Arduino Hardware Guide)
Контактная площадка соответствует спецификации Arduino Uno, обеспечивает подключение стандартных плат расширений (Shield board).
Широтно-импульсная модуляция(ШИМ, англ. pulse-width modulation (PWM))
В отличие от Arduino UNO, где доступно 6 контактов типа PWM, на Intel Edison Board можно задействовать только 4 из 6 доступных контактов.
Выходную частоту PWM и коэффициент заполнения можно определить по формуле:
  • Заданная частота ~ = 19,2 МГц * Base_unit value(заданное значение) / 256
  • Коэффициент заполнения PWM  ~ = PWM_on_time_divisor / 256

За переключение отвечают перемычки (джампера) J11 и J12. Более подробно на страницах 15-16

Аналого-цифровой преобразователь (АЦП, англ. Analog-to-digital converter, ADC)

В качестве АЦП используется микросхема ADS7951 A/D, со следующими характеристиками:

  • 12-bit A/D
  • 8 каналов
  • 1 MHz частота выборок
  • 70 SNR, дБ

Диапазоны напряжений от 0 до 2.5 V или от 0 до 5 V, регулируется перемычкой(AREF или IOREF перемычка на плате J8)

Интерфейс USB

На плате размещено три порта USB 2.0. Порт J3 используется в качестве UART интерфейса. Порт J16 работает в режиме устройство (device). Порт J6 работает в режиме хаб(host port, для подключения внешний устройств). Но порты J16 и J6 одновременно не могут работать, для переключения порта используется переключатель SW1. Для включения порта J6 USB HUB, необходимо передвинуть переключатель в сторону этого порта. В противном случае будет использоваться порт J16, и Intel Edison будет работать как USB устройство (device).
При переключение режима работы Intel Edison как USB HUB, обязательно необходимо подать внешнее питание.

Электропитание

Intel Edison специально спроектирован для низкого энергопотребления. При средней нагрузке питание обычно не превышает 200 mA, в случае использования Wi-Fi потребление возрастает до 430 mA. Плата Intel Edison Board для Arduino может работать от порта USB(в режиме устройство) или от внешнего источника питания от 7 до 15 V.
Для проектом с низким энергопотреблением, можно подключить литий-ионный аккумулятор (Li-ion) (3.0 до 4.3 Vmax) к разъему J2. В этом режиме на платы расширения будет подано напряжение 3.3 V и максимальная сила тока составит 100 mA.
Более подробно на страницах 17-20
Индикаторы(LEDs)
На плате размещено 3 индикатора:

  • DS1 – индикатор рабочего состояния. Включен во время работы Intel Edison, и выключен в случае перезагрузки процессора RESET_OUT# low
  • DS2 – стандартный индикатор для Arduino плат. Соответствует 13 цифровому контакту. Прямой доступ из Arduino кода.
  • DS3 – индикатор зарядки батареи. Включен во время зарядки подключаемой батареи.

Кнопки
Общий сброс (System reset) – кнопка SW1UI5. Полная перезагрузка Intel Edison, включая все подключенные подули.
Сброс платы расширения(Shield reset) – кнопка SW1UI1. Сброс платы расширения(Shield board), при этом вычислительные операции  Intel Edison не прерываются.
Подача питания(Power button) — кнопка SW1UI2. Однократное нажатие приведёт к переводу  Intel Edison в режим сна(пониженное энергопотребление). Повторное нажатие, выключит модуль полностью.
Купить
Приобрести Intel Edison можно в магазинах:
Или в более доступных китайских:

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.